Second Life of a Hungarian SharePoint Geek

December 23, 2010

How to create WssId for terms that are not yet referenced from the site

Filed under: Reflection, SP 2010, Taxonomies — Tags: , , — Peter Holpar @ 00:45

About a year ago I wrote about TaxonomyFieldValue and its WssId property. In a recent comment I got the question how to get the WssId for terms that are not referenced by the list items on the site.

I tried to answer the question there, but to provide a full example, I decided to post a code sample as well.

As described in the original post, the lookup item for the term and the corresponding WssId is created on the site through the private AddTaxonomyGuidToWss method.

One could call that method directly using reflection to generate the WssId for a term not yet in use on the site.

The following method – having the same name and signature as the original one – illustrates this call:

  1. private int AddTaxonomyGuidToWss(SPSite site, Term term, bool isKeywordField)
  2. {
  3.     int result = -1;
  4.  
  5.     Type taxonomyFieldType = typeof(TaxonomyField);
  6.  
  7.     MethodInfo mi_AddTaxonomyGuidToWss = taxonomyFieldType.GetMethod("AddTaxonomyGuidToWss",
  8.             BindingFlags.NonPublic | BindingFlags.Static, null,
  9.             new Type[3] { typeof(SPSite), typeof(Term), typeof(bool) },
  10.             null
  11.             );
  12.     if (mi_AddTaxonomyGuidToWss != null)
  13.     {
  14.         result = (int)mi_AddTaxonomyGuidToWss.Invoke(null, new object[3] { site, term, isKeywordField });
  15.     }
  16.  
  17.     return result;
  18. }

The original AddTaxonomyGuidToWss method returns the WssId for the lookup item created for the term on the site or –1 if the creation is failed for some reason. Our wrapper method also returns –1 as the default value.

Let’s see a sample for its usage:

  1. private void CreateTermWssIdTest(SPSite site)
  2. {
  3.     TaxonomySession session = new TaxonomySession(site);
  4.     TermStore termStore = session.TermStores["Managed Metadata Service"];
  5.     Group group = termStore.Groups["Test group"];
  6.     TermSet termSet = group.TermSets["Colors"];
  7.     Term term = termSet.Terms["Blue"];
  8.  
  9.     Console.WriteLine("Before creating local lookup item for term '{0}'", term.Name);
  10.     DisplayTermWssIdInfo(site, term);
  11.  
  12.     AddTaxonomyGuidToWss(site, term, false);
  13.  
  14.     Console.WriteLine("After creating local lookup item for term '{0}'", term.Name);
  15.     DisplayTermWssIdInfo(site, term);
  16. }
  17.  
  18. private void DisplayTermWssIdInfo(SPSite site, Term term)
  19. {
  20.     int[] wssIds = TaxonomyField.GetWssIdsOfTerm(site, term.TermStore.Id, term.TermSet.Id, term.Id, false, 1);
  21.  
  22.     Console.WriteLine("Lookup for term '{0}' using GetWssIdsOfTerm. WssId={1}", term.Name, wssIds.Length > 0 ? wssIds[0].ToString() : "Not found!");
  23.     Console.WriteLine("Lookup for term '{0}' using GetWssIdByTermId. WssId={1}", term.Name, GetWssIdByTermId(site.RootWeb, term.Id));
  24. }

First we check for the WssId of a term called “Blue” (assumed not in use on the site), then create the WssId and then try to display again the value of the WssId. For the definition of the GetWssIdByTermId method see the original post.

On a test run we should see something similar:

Before creating local lookup item for term ‘Blue’
Lookup for term ‘Blue’ using GetWssIdsOfTerm. WssId=Not found!
Lookup for term ‘Blue’ using GetWssIdByTermId. WssId=-1
After creating local lookup item for term ‘Blue’
Lookup for term ‘Blue’ using GetWssIdsOfTerm. WssId=1
Lookup for term ‘Blue’ using GetWssIdByTermId. WssId=1

Before creating the local lookup item, the static GetWssIdsOfTerm method of the TaxonomyField class returns an empty array, that is reported as Not found! by the DisplayTermWssIdInfo method. Our helper method GetWssIdByTermId returns the default –1 value.

As I’ve tested the code on a site just created for this test, this is the first lookup item in the hidden lookup list in the site root web. That is why the term added is reported as having value 1 in the WssId.

I hope this simple example helps to better understand the concept of WssId.

Advertisements

April 22, 2010

TermStoreOperationException provides no help when you need to localize the source of the error

Filed under: SP 2010, Taxonomies — Tags: , — Peter Holpar @ 14:58

Recently I work a lot with taxonomy data from code. As you might already know, there one should call the CommitAll method of the TermStore instance after you made the required modifications in the current session to commit all the changes in a single batch. If you are not familiar with it, you can find more information about this things here.

In my tool (see a former version here) I load a lot of taxonomy terms into multiple term sets. If I try to create a term that already exists I get a TermStoreOperationException that says “There is already a term with the same default label and parent term.”. The issue in this case is that you get the exception on commit, not immediately when you call the method that creates the term, and since the TermStoreOperationException provides no extra information (what parent term or what term label) about the details it is quite hard to localize the source of the problem.

Although in my case the code checked if there is a term having the same name as the new one, I ignored the fact that term labels (similar to group names and term set names) are case insensitive, and compared the labels for equality. After applying the ToLower() method to both sides of the relation the problem was eliminated.

It is OK for now, but I feel that the TermStoreOperationException was not really designed with developers in mind. It works for user interface modifications where you create a single term per transaction but it suffers when you have to work with a lot of modifications in a single transaction.

March 3, 2010

AJAX-enabling the TaxonomyWebTaggingControl

Filed under: AJAX, Reflection, SP 2010, Taxonomies — Tags: , , , — Peter Holpar @ 04:37

If you need to use the TaxonomyWebTaggingControl on a complex UI and would not like to refresh all the page if you have to change only the part where the taxonomy control is, you may try to use AJAX UpdatePanel as shown in the Visual Web Part code below.

  1. <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
  2. <%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  3. <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  4. <%@ Register Tagprefix="Taxonomy" Namespace="Microsoft.SharePoint.Taxonomy" Assembly="Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  5. <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  6. <%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
  7. <%@ Import Namespace="Microsoft.SharePoint" %>
  8. <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  9. <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="VisualWebPart1UserControl.ascx.cs" Inherits="TestWebPart.VisualWebPart1.VisualWebPart1UserControl" %>
  10.  
  11. <asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Always" RenderMode="Block" runat="server">
  12. <ContentTemplate>
  13. <Taxonomy:TaxonomyWebTaggingControl ID="TaxonomyControl" runat="server" />
  14.  
  15.  
  16. <p><asp:LinkButton ID="PostbackButton" OnClick="PostbackButton_OnClick" runat="server">Postback</asp:LinkButton></p>
  17.  
  18.  
  19. </ContentTemplate>
  20. </asp:UpdatePanel>

Of course, replace term store / group / set names to match your environment.

  1. using System;
  2. using System.Web.UI;
  3. using System.Web.UI.WebControls;
  4. using System.Web.UI.WebControls.WebParts;
  5. using Microsoft.SharePoint.Taxonomy;
  6. using Microsoft.SharePoint;
  7.  
  8. namespace TestWebPart.VisualWebPart1
  9. {
  10.     public partial class VisualWebPart1UserControl : UserControl
  11.     {
  12.         protected void Page_Load(object sender, EventArgs e)
  13.         {
  14.                 SPContext context = SPContext.Current;
  15.                 SPSite site = context.Site;
  16.                 TaxonomySession session = new TaxonomySession(site);
  17.                 TermStore termStore = session.TermStores["Metadata"];
  18.                 Group group = termStore.Groups["Test"];
  19.                 TermSet productsTermSet = group.TermSets["Products"];
  20.  
  21.                 TaxonomyControl.SspId.Add(termStore.Id);
  22.                 TaxonomyControl.TermSetId.Add(productsTermSet.Id);
  23.                 TaxonomyControl.IsAddTerms = true;
  24.                 TaxonomyControl.AllowFillIn = true;
  25.                 TaxonomyControl.IsMulti = true;
  26.         }
  27.  
  28.         protected void PostbackButton_OnClick(object sender, EventArgs e)
  29.         {
  30.             // do nothing
  31.         }
  32.     }
  33. }

On the first page load everything seems to be OK.

image

But when you click on the Postback button the taxonomy control seems to be lost as shown below.

image

To understand why it happens you should know that the TaxonomyWebTaggingControl is rendered on server side as a hidden field and a DIV element. Text field, icon added using client side scripts, as well other initialization steps required to the full functionality.

The client side script is produced by the private getOnloadJavascript method of the TaxonomyWebTaggingControl. To enable the taxonomy functionality, we should run first part of the script after the partial-page update. We can use the AJAX Sys.WebForms.PageRequestManager endRequest event to catch and inject our JavaScript to handle this requirement.

The code below shows you how to get the script we need using Reflection and register it on the page for re-initialization after the update happens.

  1. using System;
  2. using System.Web.UI;
  3. using System.Web.UI.WebControls;
  4. using System.Web.UI.WebControls.WebParts;
  5. using Microsoft.SharePoint.Taxonomy;
  6. using Microsoft.SharePoint;
  7. using Microsoft.SharePoint.Utilities;
  8. using System.Reflection;
  9. using System.Text;
  10.  
  11. namespace TestWebPart.VisualWebPart1
  12. {
  13.     public partial class VisualWebPart1UserControl : UserControl
  14.     {
  15.         protected void Page_Load(object sender, EventArgs e)
  16.         {
  17.                 SPContext context = SPContext.Current;
  18.                 SPSite site = context.Site;
  19.                 TaxonomySession session = new TaxonomySession(site);
  20.                 TermStore termStore = session.TermStores["Metadata"];
  21.                 Group group = termStore.Groups["Test"];
  22.                 TermSet productsTermSet = group.TermSets["Products"];
  23.  
  24.                 TaxonomyControl.SspId.Add(termStore.Id);
  25.                 TaxonomyControl.TermSetId.Add(productsTermSet.Id);
  26.                 TaxonomyControl.IsAddTerms = true;
  27.                 TaxonomyControl.AllowFillIn = true;
  28.                 TaxonomyControl.IsMulti = true;
  29.  
  30.                 // register the client script for taxonomy control initialization
  31.                 String key = "TaxonomyWebTaggingAjaxIncludeOnce";
  32.                 if (!this.Page.ClientScript.IsClientScriptBlockRegistered(base.GetType(), key))
  33.                 {
  34.  
  35.                     this.Page.ClientScript.RegisterClientScriptBlock(base.GetType(), key, GetReloadJavaScript(TaxonomyControl), true);
  36.                 }
  37.         }
  38.  
  39.         protected void PostbackButton_OnClick(object sender, EventArgs e)
  40.         {
  41.             // do nothing
  42.         }
  43.  
  44.         private string GetReloadJavaScript(TaxonomyWebTaggingControl taxonomyControl)
  45.         {
  46.             String script = String.Empty;
  47.  
  48.             String containerId = SPEncode.ScriptEncode(taxonomyControl.Controls[1].ClientID);
  49.  
  50.             Type type_TaxonomyWebTaggingControl = typeof(TaxonomyWebTaggingControl);
  51.  
  52.             MethodInfo mi_getOnloadJavascript = type_TaxonomyWebTaggingControl.GetMethod("getOnloadJavascript", BindingFlags.NonPublic | BindingFlags.Instance);
  53.             String fullScript = (String)mi_getOnloadJavascript.Invoke(taxonomyControl, null);
  54.             int pos = fullScript.IndexOf(String.Format("function {0}_load()", containerId));
  55.  
  56.             if (pos > –1)
  57.             {
  58.                 StringBuilder builder = new StringBuilder();
  59.                 builder.Append("var myPrm = Sys.WebForms.PageRequestManager.getInstance();");
  60.                 builder.Append("myPrm.add_endRequest(EndRequest);");
  61.                 builder.Append("function EndRequest(sender, args)");
  62.                 builder.Append("{");
  63.                 // we get te first part of the script needed to initialization
  64.                 // we start from pos 1, because we don't need the leading '{'
  65.                 builder.Append(fullScript.Substring(1, pos – 1));
  66.                 builder.Append("Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad('");
  67.                 builder.Append(containerId);
  68.                 builder.Append("');");
  69.                 builder.Append("}}");
  70.  
  71.                 script = builder.ToString();
  72.             }
  73.  
  74.             return script;
  75.         }
  76.     }
  77. }

Validation messages for the TaxonomyWebTaggingControl

Filed under: Reflection, SP 2010, Taxonomies — Tags: , , — Peter Holpar @ 03:41

When working with the TaxonomyWebTaggingControl it is important to be able to identify the source of a validation issue. In this task Reflector helps a lot but useful to know which specific error constant belongs to the validation message you see on the user interface.

The validation process call chain in this control is as follows:

Validate —-> ResolveUnboundTerm ––> AddNewKeyword
             \—>   ResolveBoundTerm —> AddNewTerm

The methods contain the following error message references (see Resources.GetString calls in the methods):

 

Validate

TermNotValid
NotMultipleValueType
BadDataFromTaggingUI
AmbiguousTerm
InlineEditNotSupportedWithMultipleTermSets
ErrorTermStoreNotFound

ResolveUnboundTerm

AmbiguousTerm
ClosedTermSetError
ErrorTermStoreNotFound

ResolveBoundTerm

AmbiguousTerm
ClosedTermSetError

AddNewKeyword

ErrorDuplicatedUnavailableTerm
TermSetNotAvailableOrOpenForTermCreation
TermNotValid

AddNewTerm TermStoreDefaultKeywordsNotSet

It is easy to find the real validation error messages using a code like this:

  1. private void ResourceStringTest()
  2. {
  3.     String[] resourceNames = new String[] {
  4.         "TermNotValid",
  5.         "NotMultipleValueType",
  6.         "BadDataFromTaggingUI",
  7.         "InlineEditNotSupportedWithMultipleTermSets",
  8.         "ErrorTermStoreNotFound",
  9.         "AmbiguousTerm",
  10.         "ClosedTermSetError",
  11.         "TermStoreDefaultKeywordsNotSet",
  12.         "ErrorDuplicatedUnavailableTerm",
  13.         "TermSetNotAvailableOrOpenForTermCreation"
  14.     };
  15.  
  16.     Array.ForEach(resourceNames, resourceName => Console.WriteLine("{0}: {1}", resourceName, GetResourceString(resourceName)));
  17.  
  18. }
  19.  
  20. private String GetResourceString(String resourceName)
  21. {
  22.     String resourceString = String.Empty;
  23.  
  24.     // hack to get the Microsoft.SharePoint.Taxonomy assembly
  25.     Assembly taxonomy = typeof(TaxonomyField).Assembly;
  26.     // Resources is an internal class, so you cannot get it directly by defining the type from Visual Studio
  27.     Type resourcesType = taxonomy.GetType("Microsoft.SharePoint.Taxonomy.Internal.Resources");
  28.  
  29.     Type[] parameterTypes = new Type[1] { typeof(String) };
  30.     MethodInfo mi_GetString = resourcesType.GetMethod("GetString", BindingFlags.NonPublic | BindingFlags.Static,
  31.         null, parameterTypes, null);
  32.     if (mi_GetString != null)
  33.     {
  34.         resourceString = (String)mi_GetString.Invoke(null, new object[] { resourceName });
  35.     }
  36.  
  37.     return resourceString;
  38.  
  39. }

And it is the formatted result of the run:

TermNotValid The given term is not valid
NotMultipleValueType This field does not allow multiple values
BadDataFromTaggingUI The data returned from the tagging UI was not formatted correctly
AmbiguousTerm There is more than one match for your term. Please disambiguate which term you would like to use
InlineEditNotSupportedWithMultipleTermSets Inline adding not supported with multiple term sets
ErrorTermStoreNotFound The term store could not be accessed.
ClosedTermSetError Creating a new value is not allowed in this field, please select from the existing terms.
ErrorDuplicatedUnavailableTerm The term used is unavailable for tagging.  Please select a new term
TermSetNotAvailableOrOpenForTermCreation Term set not available or open for term creation
TermStoreDefaultKeywordsNotSet The site does not contain a default keywords termstore

You can see that it is sometimes trivial to guess the short form of the warning from the full error message (and vice versa), but in some cases it would be hard to guess without this little trick.

Taxonomy issues

Filed under: Bugs, SP 2010, Taxonomies — Tags: , , — Peter Holpar @ 03:07

During the work with the taxonomy / managed metadata in the past few weeks I found few things that seem at least not a fortunate design decision but some of these are definitely bugs.

Issues with the pipe

When you edit a list item that has a managed metadata field  and you type a single pipe character (“|”) in the text box for the managed metadata field and try to save your item, you get the following exception:

[SPException: Failed to get value of the "YourMetadataColumnName" column from the "Managed Metadata" field type control.  See details in log. Exception message: Value cannot be null.
Parameter name: termId.]
   Microsoft.SharePoint.WebControls.BaseFieldControl.OnLoad(EventArgs e) +21499625
   Microsoft.SharePoint.Taxonomy.TaxonomyFieldControl.OnLoad(EventArgs e) +25
   System.Web.UI.Control.LoadRecursive() +66
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2428

——————————————————————————–
Version Information: Microsoft .NET Framework Version:2.0.50727.4927; ASP.NET Version:2.0.50727.4927

When you type a text that contains pipe character (“|”) in the text box for the managed metadata field and try to save your item, the text box is cleared (in contrast to the standard underline and red font color) and you get the following standard validation message:

Creating a new value is not allowed in this field, please select from the existing terms.

Fill-in or not fill-in that is the question

When you have a managed metadata field you can allow fill-in choices as shown below.

image

 

image

image

To enable creating new taxonomy values from the text box, you should set the CreateValuesInEditForm property for your field to true.

  1. SPList list = web.Lists["YourList"];
  2.  
  3. //// to allow tagging on edit form
  4. TaxonomyField taxonomyField = (TaxonomyField)list.Fields["YourField"];
  5. taxonomyField.Open = true;
  6. taxonomyField.CreateValuesInEditForm = true;
  7. taxonomyField.Update();

Setting the CreateValuesInEditForm property is required due to the code in the CreateChildControls method of the TaxonomyFieldControl:

this.webTaggingUI.IsAddTerms = field.Open && (field.IsKeyword || field.CreateValuesInEditForm);

In my code above I set the Open property to true as well, since I had issues when the Open property was not reflected changes made through code in the term set Open / Closed settings.

BTW, do you know why it takes so much time to save term set properties, like Open / Closed changes when working with the Term Store Management Tool UI comparing doing the same from my custom code? Please, let me know! Probably that is because the UI makes some additional work as well, like updating properties of managed metadata fields referring to the term set (see my former issue with the Open property), but I had no time to investigate that in depth.

And there is the anchor

But wait a minute! Setting the CreateValuesInEditForm property does not help in all cases. For example, assume you don’t set the the managed metadata field to be bound to the root of the term set but to a subterm in the term set hierarchy. In code it means that the AnchorId property of your TaxonomyField instance is not Guid("{00000000-0000-0000-0000-000000000000}").

image

In this case when you would like to add a new value by typing it in the text box for the managed metadata field and saving your item. Your value will be underlined and red in the text box but without any validation error. When you check the term set, you find the value was created in the root of the term set instead of the specified subterm level, so it is really not an option for selection.

It is due to the followings. When saving the values, the Validate method of the TaxonomyWebTaggingControl is called that calls the ResolveBoundTerm method that calls the AddNewTerm method. The AddNewTerm method creates the new term through the CreateTerm method, but it does not handle the AnchorId property.

Current code:

term = termSet.CreateTerm(label, termSet.TermStore.WorkingLanguage);

Should be modified by MS:

TermSetItem termParent = (this.AnchorId == new Guid("{00000000-0000-0000-0000-000000000000}")) ? termSet : termSet.GetTerm(this.AnchorId);

term = termParent.CreateTerm(label, termSet.TermStore.WorkingLanguage);

The long, long wait

When you type a text in the text box for the managed metadata field and try to save your item, receiving the "Creating a new value is not allowed in this field, please select from the existing terms." validation message, you can try to resolve the issue by selecting from an existing values as suggested. But when you click on the “Browse for a valid choice” icon right to the text box, the picker is displayed, but the status remains loading and the terms are not displayed.

image

Accepting suggested values

If your managed metadata field allows multiple values, and you type the first value, the control suggests you possible terms. You can accept the suggestions by pressing the Tab key. If you type further after this, the suggested terms appear for the second value, but pressing the Tab has no effect in this case. This issue is a minor one, although can be rather annoying sometimes when working only with the keyboard.

February 15, 2010

Build your own user interface components using the taxonomy controls

Filed under: SP 2010, Taxonomies — Tags: , — Peter Holpar @ 04:48

If you would like to use the built-in taxonomy controls of SharePoint 2010 your best choice might be to go with the TaxonomyWebTaggingControl. This is the class the field control of the TaxonomyField, TaxonomyFieldControl itself uses internally to render its content, although it does not expose the full capability of the control.

In this post I will show you some features of the TaxonomyWebTaggingControl using it in a visual web part project.

First, let’s see how to use it declaratively. Be sure Microsoft.SharePoint.Taxonomy is registered in your user control (.ASCX) file:

<%@ Register Tagprefix="Taxonomy" Namespace="Microsoft.SharePoint.Taxonomy" Assembly="Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Having this registration, you can add TaxonomyWebTaggingControl using the following declaration:

<Taxonomy:TaxonomyWebTaggingControl AllowFillIn="true" IsMulti="true" SSPList="{29ff8c4f-37a6-4d42-8cef-f610f0a7c7e9}"  TermSetList="{a22cf1f5-427e-41d7-8af4-7aa4dc47197d}" AnchorId="{ec5ee636-f35c-43bd-92a0-56016a0a127c}" runat="server" />

This control instance allows multiple items and filling in values. The SSPList  attribute is to specify the Id of the term store(s) and the TermSetList attribute is to specify the Id of the term set(s). Note, that none of these two attributes are automatically suggested by IntelliSense or recognized by the compiler in Visual Studio 2010 beta 2, but they works as expected. You can specify multiple values in both of these attributes, separating them by semicolons (‘;’). It is a great way to combine multiple term sets even from multiple term stores. The AnchorId attribute sets the Id of the term that serves as the starting point of the term hierarchy used in the control. Only terms below the anchor will be suggested in the control.

Although it is not so hard to get these Ids using a little command line utility, specifying these settings by Guid values is not user friendly. So let’s switch to the coding approach, but before doing that, we add some additional content to the user control:

  1. <Taxonomy:TaxonomyWebTaggingControl ID="TestTaxonomyControl" runat="server" />
  2.  
  3. <asp:Button ID="SendButton" runat="server" Text="Send"
  4.     onclick="SendButton_Click" />
  5. <br />
  6. <br />
  7. <asp:Label ID="MetaDataLabel" runat="server" Text=""></asp:Label>

Having this done, we can alter to the code behind of the user control to the following:

  1. using System;
  2. using System.Web.UI;
  3. using System.Web.UI.WebControls;
  4. using System.Web.UI.WebControls.WebParts;
  5. using Microsoft.SharePoint.Taxonomy;
  6. using Microsoft.SharePoint;
  7. using System.Drawing;
  8.  
  9. namespace VisualWebPartTest1.VisualWebPart1
  10. {
  11.     public partial class VisualWebPart1UserControl : UserControl
  12.     {
  13.         protected void Page_Load(object sender, EventArgs e)
  14.         {
  15.             SPContext context = SPContext.Current;
  16.             SPSite site = context.Site;
  17.             TaxonomySession session = new TaxonomySession(site);
  18.             TermStore termStore = session.TermStores["MyTermStore"];
  19.             Group group = termStore.Groups["MyGroup"];
  20.             TermSet productsTermSet = group.TermSets["Products"];
  21.             TermSet languagesTermSet = group.TermSets["Languages"];
  22.  
  23.             TestTaxonomyControl.SspId.Add(termStore.Id);
  24.             TestTaxonomyControl.TermSetId.Add(productsTermSet.Id);
  25.             TestTaxonomyControl.TermSetId.Add(languagesTermSet.Id);
  26.             TestTaxonomyControl.AllowFillIn = true;
  27.             TestTaxonomyControl.IsMulti = true;
  28.         }
  29.  
  30.         protected void SendButton_Click(object sender, EventArgs e)
  31.         {
  32.             String validationMessage;
  33.             bool valid = TestTaxonomyControl.Validate(out validationMessage);
  34.  
  35.             if (valid)
  36.             {
  37.                 MetaDataLabel.Text = String.Empty;
  38.                 MetaDataLabel.ForeColor = Color.Black;
  39.                 TaxonomyFieldValueCollection values = new TaxonomyFieldValueCollection(String.Empty);
  40.                 values.PopulateFromLabelGuidPairs(TestTaxonomyControl.Text);
  41.  
  42.                 foreach (TaxonomyFieldValue value in values)
  43.                 {
  44.                     MetaDataLabel.Text += value.WssId + " / " + value.TermGuid + " / " + value.Label + "<br />";
  45.                 }
  46.             }
  47.             else
  48.             {
  49.                 MetaDataLabel.ForeColor = Color.Red;
  50.                 MetaDataLabel.Text = validationMessage;
  51.             }
  52.         }
  53.     }
  54. }

As you can see we use the SspId and TermSetId properties of the TaxonomyWebTaggingControl control to specify the term store and term sets. Both of this properties are of type List<Guid>. We specify two term sets for this control, one for the Languages and another one for Products.

image

Just a thought regarding the figure above. It seems that pressing Tab button to autocomplete the suggested value only works in the case of the first tag in taxonomy controls. I hope it is only a bug in beta 2 and will be corrected in the final version.

We handle the Click event of the SendButton control to validate the values specified in our taxonomy control. We can use the Validate method of the TaxonomyWebTaggingControl control for that. This method returns with a boolean value that represents the result of the validation, and an out String parameter that is the validation error message.

If the validation fails we display the error message. Note that the text box is empty now, since we don’t set its value on post back.

image

BTW, if you want the control to be able to add new terms:

  • There should be only a single term set attached to the control.
  • The term set must be open.
  • You should set the IsAddTerms property to true.

On success we display the selection values. The Text property of the TaxonomyWebTaggingControl control contains the label – Id pairs of the selected terms. To convert it to TaxonomyFieldValueCollection, we should use the PopulateFromLabelGuidPairs method.

image

Note, that the WssId is –1 for both values, since in this case the control is not bound to a site, not like in the case of a TaxonomyField (see my former post about the WssId).

February 11, 2010

Adding a managed metadata column to a list via SharePoint Server 2010 object model

Filed under: SP 2010, Taxonomies — Tags: , — Peter Holpar @ 16:24

Ali Mazaheri has posted a similar code recently, but since that was for content type, not list fields, and I found there are some minor difference between the two, I’ll post my solution for adding managed metadata type field to lists.

To tell the truth, I have not tried adding this kind of field to content type, but “translated” the code for handling the list fields instead, and extended with some new features.

I should note, that first I’ve tried to update the field either it was just added  or was added previously, but I’ve received a <nativehr>0x80070057</nativehr><nativestack></nativestack> error when tried to update the newly added field. Probably it was because in this case the field had its Id and InternalName properties empty yet.

  1. AddTaxonomyField(SPList list, bool mayExist, String name, bool required, String description,
  2.     bool addToDefaultView, bool noCrawl, String termStoreName, String groupName, String termSetName, String anchorPath,
  3.     bool allowsMultiple, bool enforceUniqueValues)
  4. {
  5.     SPWeb web = list.ParentWeb;
  6.     TaxonomySession session = new TaxonomySession(web.Site);
  7.     TermStore termStore = session.TermStores[termStoreName];
  8.     Group group = termStore.Groups[groupName];
  9.     TermSet termSet = group.TermSets[termSetName];
  10.  
  11.     TaxonomyField field = null;
  12.     bool fieldExists = false;
  13.  
  14.     // if we allow the field to exist ant it really exist
  15.     // then we get the reference for the instance
  16.     // it will throw an exception if the field exists
  17.     // but has a different type
  18.     if ((mayExist) && (list.Fields.ContainsField(name)))
  19.     {
  20.         field = (TaxonomyField)list.Fields[name];
  21.         fieldExists = true;
  22.     }
  23.     // otherwise we create a new instance
  24.     // it will throw an exception if the field exists
  25.     // and we would like to recreate that
  26.     else
  27.     {
  28.         field = (TaxonomyField)list.Fields.CreateNewField("TaxonomyFieldType", name);
  29.     }
  30.  
  31.     field.Required = required;
  32.     field.Description = description;
  33.     field.SspId = termStore.Id;
  34.     field.TermSetId = termSet.Id;
  35.     field.AllowMultipleValues = allowsMultiple;
  36.     field.EnforceUniqueValues = enforceUniqueValues;
  37.     // field must be indexed to enfoce unique values!
  38.     field.Indexed = enforceUniqueValues;
  39.  
  40.     if (!String.IsNullOrEmpty(anchorPath))
  41.     {
  42.         // see GetTermByPath method in my former post
  43.         Term anchor = termSet.GetTermByPath(anchorPath);
  44.         field.AnchorId = anchor.Id;
  45.     }
  46.  
  47.     field.NoCrawl = noCrawl;
  48.  
  49.     if (!fieldExists)
  50.     {
  51.         list.Fields.Add(field);
  52.     }
  53.     else
  54.     {
  55.         field.Update();
  56.     }
  57.  
  58.     if (addToDefaultView)
  59.     {
  60.         SPView view = list.DefaultView;
  61.         // we add the field if it is new or if the view does not contain already it
  62.         if ((!fieldExists) || (!view.ViewFields.Exists(field.InternalName)))
  63.         {
  64.             view.ViewFields.Add(field.Title);
  65.             view.Update();
  66.         }
  67.     }
  68.  
  69.     return field;
  70. }

For the GetTermByPath method I used to get the value of the AnchorId property see my former post. Anchor is a term that refers to the Term that is the starting point of the “navigation” within a TermSet. Users can select only Term that are under the anchor Term.

February 10, 2010

Getting Term by the path in the taxonomy hierarchy

Filed under: SP 2010, Taxonomies — Tags: , — Peter Holpar @ 18:06

I’ve not found any built-in method to get a specific Term in the taxonomy hierarchy by specifying its path. I mean the name of the parent terms from the root TermSet and the name of the term itself, like “parent of the parent term name/parent term name/term name”. Terms having the same name may exist at multiple levels or branches of the taxonomy, so the name of the term is not necessarily unique within a TermSet, one can not use that to look up the exact Term.

So I’ve created an extension method that does just what I need, here is the code:

  1. public static class Helper
  2. {
  3.     public static Term GetTermByPath(this TermSet termSet, String path)
  4.     {
  5.         Term result = null;
  6.  
  7.         String[] pathElements = path.Split(new Char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
  8.  
  9.         int depth = 0;
  10.         result = termSet.Terms[pathElements[depth]];
  11.  
  12.         while (depth < pathElements.Length-1)
  13.         {
  14.             depth++;
  15.             result = result.Terms[pathElements[depth]];
  16.         }
  17.  
  18.         return result;
  19.     }
  20. }

In the following code we assume a term set hierarchy that was imported from the sample import CSV as I’ve discussed in a former post.

  1. TaxonomySession session = new TaxonomySession(site);
  2. TermStore termStore = session.TermStores["Managed Metadata Service"]; // replace it to fit your configuration
  3. Group group = termStore.Groups["GroupName"]; // replace it to fit your configuration
  4.  
  5. TermSet termSet = group.TermSets["Political Geography"];
  6. Term term = termSet.GetTermByPath("Continent/Political Entity/Country/Province or State/County or Region/City/District");

As usually the codes above are only for illustration purposes. If you would like to use them in a production code, you should add some kind of exception handling to them.

And please, let me know if you find this method in the standard API!

February 8, 2010

Exporting TermSet and Group – under construction, and other tales about taxonomy export / import

Filed under: Reflection, SP 2010, Taxonomies — Tags: , , — Peter Holpar @ 19:19

It seems that exporting TermSet and Group hierarchies in the Beta 2 bits is not ready to use. The source of the Export methods for these classes shows that after a few lines of tracing it simply throws a NotImplementedException.

At first sight it’s even stranger that there is no Import method corresponding to Export – at least you cannot find that neither in the TermSet class nor in the related classes, like TermStore or TaxonomySession. So what user interface or class should we use to import taxonomies manually or from code?

You might notice that there is a Sample Import section on the Term Store Management Tool page. You should click on the name of the term store on the left side tree view to make this option visible:

image

There are some references on the web about the CSV file you should use to import term sets, but until now I have not found information about how one should use that file to import the data, and unfortunately I have not remembered this detail from SPC 2009. The TechNet article Managed metadata input file format says: “For instructions about how to import metadata, see Office.com”, but where exactly, please?!

Let’s try from the coding side! The good news is that importing has its dedicated class called ImportManager, the bad news is that its constructors are not public. But such minor issues cannot stop us from using the services of this class.

The folowing code snippet shows you how to do that using Reflection. Do not forget to add the necessary using directives to your code referencing the System.Reflection and System.IO namespaces. The code assumes you’ve saved the sample CSV file as C:\work\ImportTermSet.csv on the server.

  1. Type importManagerType = typeof(ImportManager);
  2. Type[] parameterTypes = new Type[1] { typeof(TermStore) };
  3. ConstructorInfo constInfo = importManagerType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
  4.     null, CallingConventions.ExplicitThis, parameterTypes, null);
  5.  
  6. if (constInfo != null)
  7. {
  8.     TaxonomySession session = new TaxonomySession(site);
  9.     TermStore termStore = session.TermStores["MetadataTermStoreName"];
  10.     Object[] callParams = new Object[1] { termStore };
  11.     ImportManager importManager = (ImportManager)constInfo.Invoke(callParams);
  12.     StreamReader reader = new StreamReader(@"C:\work\ImportTermSet.csv");
  13.     Group group = termStore.Groups["NameOfTheGroup"];
  14.     bool allTermsAdded;
  15.     String errorMessage;
  16.     importManager.ImportTermSet(group, reader, out allTermsAdded, out errorMessage);
  17.     Console.WriteLine("All terms added: {0}", allTermsAdded);
  18.     Console.WriteLine("Error message: {0}", errorMessage);
  19. }
  20. else
  21. {
  22.     Console.WriteLine("Error message: No constructor found");
  23. }

There is a default batch size of 1000 – I assume it means 1000 lines of items are read up before processing, than the next 1000 lines follow, etc. – , but you can change that before starting the import process using the BatchSize property.

Analyzing the ImportManager class using Reflector leads us to the user interface where you can do the import as well. The code behind of the _Layouts/inputcsvfile.aspx instantiates this class, see it on the following figure:

image

Following the call chain of JavaScript methods shows that the modal dialog is displayed by the _Layouts/termstoremanager.aspx, where I searched this a lot previously. You should first click on the group name to highlight it, then click on the small black arrow right to the group name to display the menu.

image

If you consider it is hacking to use Reflection or would like to have the source code to create an export / import solution that fits better to your needs than the original one then you can create your own tool to complete the export and import processes.

I like custom command like tools so I created my own utility to export and import term sets. I’ve chosen XML file format, that makes it possible to create term sets deeper than the 7 levels limitation of the CSV file , furthermore you can include multiple term sets in a single file.

I’ve attached the full code of the utility (reference the Microsoft.SharePoint.Taxonomy.dll assembly in your project in addition to the standard Microsoft.SharePoint.dll!), but be prepared that it is not over-tested, so you should use it at your own risk.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml;
  6. using Microsoft.SharePoint;
  7. using Microsoft.SharePoint.Taxonomy;
  8.  
  9. namespace ImportTerms
  10. {
  11.     class Program
  12.     {
  13.         //0: operation
  14.         //1: site
  15.         //2: xml file
  16.         //3: term store name
  17.         //4: group name
  18.         //5…: term set(s) name (export only!)
  19.         static void Main(string[] args)
  20.         {
  21.  
  22.             try
  23.             {
  24.  
  25.                 if (args.Length >= 5)
  26.                 {
  27.                     String operation = args[0].ToLower();
  28.                     String siteUrl = args[1];
  29.                     String dataFilePath = args[2];
  30.                     String termStoreName = args[3];
  31.                     String groupName = args[4];
  32.                     if (operation == "export")
  33.                     {
  34.                         if (args.Length > 5)
  35.                         {
  36.                             int termSetCount = args.Length – 5;
  37.                             String[] termSets = new String[termSetCount];
  38.                             Array.Copy(args, 5, termSets, 0, termSetCount);
  39.                             Export(siteUrl, dataFilePath, termStoreName, groupName, termSets);
  40.                         }
  41.                         else
  42.                         {
  43.                             ShowUsage();
  44.                         }
  45.                     }
  46.                     else if (operation == "import")
  47.                     {
  48.                         if (args.Length == 5)
  49.                         {
  50.                             Import(siteUrl, dataFilePath, termStoreName, groupName);
  51.                         }
  52.                         else
  53.                         {
  54.                             ShowUsage();
  55.                         }
  56.                     }
  57.  
  58.                 }
  59.                 else
  60.                 {
  61.                     ShowUsage();
  62.                 }
  63.             }
  64.  
  65.             catch (Exception ex)
  66.             {
  67.                 Console.WriteLine(ex.ToString());
  68.             }
  69.         }
  70.  
  71.         private static void Export(String siteUrl, String dataFilePath, String termStoreName, String groupName, String[] termSets)
  72.         {
  73.             Console.WriteLine("Connecting to server…");
  74.             using (SPSite site = new SPSite(siteUrl))
  75.             {
  76.                 XmlTextWriter writer = new XmlTextWriter(dataFilePath, Encoding.UTF8);
  77.                 writer.Formatting = Formatting.Indented;
  78.                 writer.WriteStartElement("TermSets");
  79.  
  80.                 TaxonomySession session = new TaxonomySession(site);
  81.                 TermStore termStore = session.TermStores[termStoreName];
  82.                 Group group = termStore.Groups[groupName];
  83.                 foreach (String termSetName in termSets)
  84.                 {
  85.                     Console.WriteLine("Exporting term set: {0}", termSetName);
  86.  
  87.                     writer.WriteStartElement("TermSet");
  88.  
  89.                     TermSet termSet = group.TermSets[termSetName];
  90.                     writer.WriteAttributeString("name", termSetName);
  91.                     if (!String.IsNullOrEmpty(termSet.Description))
  92.                     {
  93.                         writer.WriteAttributeString("description", termSet.Description);
  94.                     }
  95.                     writer.WriteAttributeString("isForTagging", termSet.IsAvailableForTagging.ToString());
  96.  
  97.                     foreach (Term term in termSet.Terms)
  98.                     {
  99.                         AddTerm(writer, term);
  100.                     }
  101.  
  102.                     writer.WriteEndElement(); // TermSet
  103.  
  104.                 }
  105.                 writer.WriteEndElement(); // Root
  106.                 writer.Flush();
  107.                 writer.Close();
  108.             }
  109.         }
  110.  
  111.         private static void AddTerm(XmlTextWriter writer, Term term)
  112.         {
  113.             writer.WriteStartElement("Term");
  114.  
  115.             if (!String.IsNullOrEmpty(term.GetDescription()))
  116.             {
  117.                 writer.WriteAttributeString("description", term.GetDescription());
  118.             }
  119.             writer.WriteAttributeString("name", term.Name);
  120.             writer.WriteAttributeString("isForTagging", term.IsAvailableForTagging.ToString());
  121.  
  122.             foreach (Term subTerm in term.Terms)
  123.             {
  124.                 AddTerm(writer, subTerm);
  125.             }
  126.  
  127.             writer.WriteEndElement(); // Term
  128.         }
  129.  
  130.         private static void Import(String siteUrl, String dataFilePath, String termStoreName, String groupName)
  131.         {
  132.  
  133.             Console.WriteLine("Connecting to server…");
  134.             using (SPSite site = new SPSite(siteUrl))
  135.             {
  136.                 TaxonomySession session = new TaxonomySession(site);
  137.  
  138.                 XmlDocument dataDoc = new XmlDocument();
  139.                 Console.WriteLine("Loading…");
  140.                 dataDoc.Load(dataFilePath);
  141.  
  142.                 XmlNode rootNode = dataDoc.SelectSingleNode("TermSets");
  143.  
  144.                 TermStore termStore = session.TermStores[termStoreName];
  145.                 Group group = termStore.Groups[groupName];
  146.  
  147.                 foreach (XmlNode termSetNode in rootNode.SelectNodes("TermSet"))
  148.                 {
  149.                     String termSetName = GetStringAttribute(termSetNode, "name", null, true);
  150.                     Console.WriteLine("Importing term set: {0}", termSetName);
  151.                     String description = GetStringAttribute(termSetNode, "description", null, false);
  152.                     bool isForTagging = GetBoolAttribute(termSetNode, "isForTagging", true, false);
  153.                     TermSet termSet = group.CreateTermSet(termSetName);
  154.                     if (!String.IsNullOrEmpty(description))
  155.                     {
  156.                         termSet.Description = description;
  157.                     }
  158.                     termSet.IsAvailableForTagging = isForTagging;
  159.                     AddNodes(termSet, termSetNode);
  160.                 }
  161.  
  162.                 Console.WriteLine("Commiting…");
  163.                 termStore.CommitAll();
  164.             }
  165.             Console.WriteLine("Done.");
  166.  
  167.         }
  168.  
  169.         private static void AddNodes(TermSetItem termSetItem, XmlNode termSetItemNode)
  170.         {
  171.             foreach (XmlNode termNode in termSetItemNode.SelectNodes("Term"))
  172.             {
  173.                 String termName = GetStringAttribute(termNode, "name", null, true);
  174.                 String description = GetStringAttribute(termNode, "description", null, false);
  175.                 int lcid = (int)GetIntAttribute(termNode, "lcid", termSetItem.TermStore.DefaultLanguage, false);
  176.                 bool isForTagging = GetBoolAttribute(termNode, "isForTagging", true, false);
  177.                 Term term = termSetItem.CreateTerm(termName, lcid);
  178.                 if (!String.IsNullOrEmpty(description))
  179.                 {
  180.                     term.SetDescription(description, lcid);
  181.                 }
  182.                 term.IsAvailableForTagging = isForTagging;
  183.                 AddNodes(term, termNode);
  184.             }
  185.         }
  186.  
  187.         private static string GetStringAttribute(XmlNode node, string attrName, string defaultValue, bool required)
  188.         {
  189.             string value = defaultValue;
  190.             if (node.Attributes[attrName] == null)
  191.             {
  192.                 if (required)
  193.                 {
  194.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  195.                 }
  196.             }
  197.             else
  198.             {
  199.                 value = node.Attributes[attrName].Value;
  200.             }
  201.             return value;
  202.         }
  203.  
  204.         private static bool GetBoolAttribute(XmlNode node, string attrName, bool defaultValue, bool required)
  205.         {
  206.             bool value = defaultValue;
  207.             if (node.Attributes[attrName] == null)
  208.             {
  209.                 if (required)
  210.                 {
  211.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  212.                 }
  213.             }
  214.             else
  215.             {
  216.                 value = bool.Parse(node.Attributes[attrName].Value);
  217.             }
  218.             return value;
  219.         }
  220.  
  221.         private static int? GetIntAttribute(XmlNode node, string attrName, int? defaultValue, bool required)
  222.         {
  223.             int? value = defaultValue;
  224.             if (node.Attributes[attrName] == null)
  225.             {
  226.                 if (required)
  227.                 {
  228.                     throw new ApplicationException(String.Format("Missing attribite {0}", attrName));
  229.                 }
  230.             }
  231.             else
  232.             {
  233.                 value = int.Parse(node.Attributes[attrName].Value);
  234.             }
  235.             return value;
  236.         }
  237.  
  238.  
  239.         private static void ShowUsage()
  240.         {
  241.             String appName = GetAppName();
  242.             Console.WriteLine("Usage: \r\n   {0} export [SieUrl] [ExportFileName] \r\n  [TermStoreName] [GroupName] [TermSetName1] [TermSetName2] … [TermSetNameN]", appName);
  243.             Console.WriteLine("or\r\n");
  244.             Console.WriteLine("{0} import [SieUrl] [ImportFileName] \r\n  [TermStoreName] [GroupName]", appName);
  245.             Console.WriteLine(@"Example: \r\n   {0} export http://sp2010 c:\temp\export.xml \r\n  Metadata MyGroup Products Languages Countries", appName);
  246.             Console.WriteLine(@"or\r\n   {0} import http://sp2010 c:\temp\import.xml \r\n  Metadata MyGroup", appName);
  247.  
  248.         }
  249.  
  250.         private static String GetAppName()
  251.         {
  252.             String path = Environment.GetCommandLineArgs()[0];
  253.             int startName = path.LastIndexOf(@"\") + 1;
  254.             return path.Substring(startName);
  255.         }
  256.     
  257.     }
  258.  
  259. }

And here is a simple import file for your convenience as well:

  1. <TermSets>
  2.   <TermSet name="Language">
  3.     <Term name="English" />
  4.     <Term name="French" />
  5.     <Term name="German" />
  6.   </TermSet>
  7. </TermSets>

February 3, 2010

Some words about TaxonomyFieldValue and its WssId property

Filed under: SP 2010, Taxonomies — Tags: , — Peter Holpar @ 11:42

In these weeks I’m busy with planning and POCing a custom social site solution built on top of SP 2010. Lot of the requirements are related to the new managed metadata framework, so I’m planning to post some of the most exciting results in my blogs.

In this post I will introduce you the not-so-obvious WssId property of the TaxonomyFieldValue class.

TaxonomyFieldValue has three important properties (at least, for now):

Label (string): It is the Value (string) property of the Label selected by the user from the Labels property of the Term object

TermGuid (string, not Guid): it is the Id (Guid) property of the Term (inherited from TaxonomyItem)

WssId (int): What is that and where its value comes from?

When you assign value a TaxonomyField, an item is created for the selected Term (or if there is already an item for the term, that item is used)  in a hidden list called TaxonomyHiddenList in the root web of the site. The WssId is the Id of that item in the TaxonomyHiddenList .

To track the call chain of this process, see the following internal or private methods of TaxonomyField (if you don’t like boring deep technical content, you can skip this section):

The ValidateTaxonomyFieldValue is to validate the field value. First if the value is not yet resolved it tries to resolve that using the GetOrResolveUnvalidatedTaxonomyFieldvalue method. Both of these methods call the AddTaxonomyGuidToWss method, that calls with elevated privileges the AddTaxonomyGuidToWssCore method. This method calls first the GetLookupList method to get the TaxonomyHiddenList for the current site through the GetLookupListHelper method. There is a private static class of type ThreadSafeDictionary<Guid, Guid> called  lookupListIDs to help this lookup process and make that faster. After creating the new item in the hidden list, the  AddTaxonomyGuidToWssCore method returns with the ID of the new item.

But where should we get the WssId from if we have only a Term object that has not this information?

I’ve found two alternatives. The first – trivial – one is the static GetWssIdsOfTerm method of the TaxonomyField class:

int[] wssIds = TaxonomyField.GetWssIdsOfTerm(site, term.TermStore.Id, term.TermSet.Id, term.Id, false, 1);

wssIds[0] will contain the ID we need.

The other way is a bit more complicated but it illustrates how to work with the TaxonomyHiddenList list:

  1. private int GetWssIdByTermId(SPWeb rootWeb, Guid termId)
  2. {
  3.     int result = –1;
  4.  
  5.     if (rootWeb.Properties.ContainsKey("TaxonomyHiddenList"))
  6.     {
  7.         Guid taxonomyHiddenListId = new Guid(rootWeb.Properties["TaxonomyHiddenList"]);
  8.         SPList taxonomyHiddenList = rootWeb.Lists[taxonomyHiddenListId];
  9.         SPQuery query = new SPQuery();
  10.         // we might have included the IdForTermSet in the query but we assume that Guid is really unique
  11.         // so there should not be temrs in other terms sets having the same ID
  12.         query.Query = String.Format(@"<Where><Eq><FieldRef Name='IdForTerm' /><Value Type='Text'>{0}</Value></Eq></Where>", termId);
  13.         SPListItemCollection items = taxonomyHiddenList.GetItems(query);
  14.         if (items.Count == 1)
  15.         {
  16.             result = int.Parse(items[0]["ID"].ToString());
  17.         }
  18.     }
  19.     return result;
  20. }

OK, but why is it good for us to know the WssId of the TaxonomyFieldValue?

There might be several reasons, but one of them is that it can be used to query the list items through the CAML syntax, as illustrated:

  1. private SPListItemCollection GetItemsByTerm(SPList list, String fieldName, Term term)
  2. {
  3.     SPSite site = list.ParentWeb.Site;
  4.     SPWeb rootWeb = site.RootWeb;
  5.  
  6.     int wssId = GetWssIdByTermId(rootWeb, term.Id);
  7.     
  8.     // or we could use this one
  9.     // wssIds[0] will be the WssId we need
  10.     //int[] wssIds = TaxonomyField.GetWssIdsOfTerm(site, term.TermStore.Id, term.TermSet.Id, term.Id, false, 1);
  11.  
  12.     SPQuery query = new SPQuery();
  13.     query.Query = String.Format(@"<Where><Eq><FieldRef Name='Metadata' LookupId='TRUE' /><Value Type='Lookup'>{0}</Value></Eq></Where>", wssId);
  14.     SPListItemCollection items = list.GetItems(query);
  15.     return items;
  16. }

You could use the following format either:

<Where><Contains><FieldRef Name=’YourTaxonomyFieldName’ /><Value Type=’Text’>Title of your term</Value></Contains></Where>

But the above syntax queries the items only as text, not by reference ID.

To call the GetItemsByTerm method:

  1. SPListItemCollection result = GetItemsByTerm(list, "YourTaxonomyFieldName", term);
  2.  
  3. foreach (SPListItem item in result)
  4. {
  5.     Console.WriteLine("Title: {0}", item["Title"]);
  6. }

It is even easier to query items if you have a TaxonomyFieldValue:

  1. private SPListItemCollection GetItemsByTaxonomyFieldValue(SPList list, String fieldName, TaxonomyFieldValue value)
  2. {
  3.     SPSite site = list.ParentWeb.Site;
  4.     SPWeb rootWeb = site.RootWeb;
  5.  
  6.     SPQuery query = new SPQuery();
  7.     query.Query = String.Format(@"<Where><Eq><FieldRef Name='Metadata' LookupId='TRUE' /><Value Type='Lookup'>{0}</Value></Eq></Where>", value.WssId);
  8.     SPListItemCollection items = list.GetItems(query);
  9.     return items;
  10. }

To call the GetItemsByTaxonomyFieldValue method:

  1. SPListItemCollection result = GetItemsByTaxonomyFieldValue(list, "YourTaxonomyFieldName", value);
  2.  
  3. foreach (SPListItem item in result)
  4. {
  5.     Console.WriteLine("Title: {0}", item["Title"]);
  6. }

The code samples here show only a simple CAML query, you can create more complex ones, for example combining multiple conditions using logical And or Or to look up items having all or any of the meta data applied.

Blog at WordPress.com.