Showing posts with label Managed Metadata Service. Show all posts
Showing posts with label Managed Metadata Service. Show all posts

Wednesday, April 28, 2010

SP 2010 - Configure and Use a TaxonomyWebTaggingControl

If you are using the Managed Metadata Service and developing a custom web part, you may be interested in using that nice term picker that you see when you create a new list item that contains a Managed Metadata Column.

First, let's cover the bare minimum code you'll need to get the term picker to show up in your web part. First you'll want to add a reference to Microsoft.SharePoint.Taxonomy to your project. Then you'll need a Register directive in your ascx, like this:

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

Now you can add the picker to your web part just like any other control.



You'll notice that I didn't set any properties on the TaxonomyWebTaggingControl. Well there are three properties you'll need to set, but we'll do it programatically. Those properties are:
  • SSPList - This property parses your string and builds a List to set the SspId property
  • GroupId - A Guid
  • TermSetList - This property parses your string and builds a List to set the TermSetId property

Now, you can set these properties declaratively if you wish, but it won't be a very portable web part, as the Guids aren't part of the import CSV format for the Managed Metadata Service, nor do any of the Taxonomy Create methods allow setting of Ids. Because of this, it's best to have your web part set these properties on the TaxonomyWebTaggingControl programatically. If you just want to see how you would go about setting these properties declaratively, scroll to the bottom of this post, as I've included it for reference.

In order to make your web part portable, you're going to need to query the Managed Metadata Service and get these values yourself. Moreover, you're going to have to do it on every load of your web part since these properties are not stored anywhere between page loads.

Note: If you only set these properties on page load, this will work as long as there are no scenarios where you need to redraw the control again, such as having a custom server validator. In that instance, your picker would work before the postback, but the look-ahead feature would be broken after the postback.

In order to make loading our properties a little easier, here is an extension method for TaxonomyWebTaggingControl that allows you to quickly set the properties. The path part of this was inspired by PHolpar. Also, remember that extension methods must be defined in static classes.

public static bool Configure(this TaxonomyWebTaggingControl twtc, string termSetPath)
{
 bool configured = false;

 TaxonomySession session = new TaxonomySession(SPContext.Current.Site);

 string[] parts = termSetPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
 if (parts.Length > 0)
 {
  TermStore termStore = GetTermStore(session, parts[0]);
  if (termStore != null && parts.Length > 1)
  {
   twtc.SSPList = termStore.Id.ToString();

   Group group = GetGroup(termStore, parts[1]);
   if (group != null && parts.Length > 2)
   {
    twtc.GroupId = group.Id;

    TermSet termSet = GetTermSet(group, parts[2]);
    if (termSet != null)
    {
     twtc.TermSetList = termSet.Id.ToString();
     configured = true;
    }
   }
  }
 }

 return configured;
}

In order to load MyTermSet, which lives within MyGroup, which resides within MyTermStore, you will use this extension method similar to the following:

protected void Page_Load(object sender, EventArgs e)
{
 bool configured = twtcSpecialty.Configure("MyTermStore/MyGroup/MyTermSet");
 if (!configured)
 {
  throw new ApplicationException("Unable to find target TermSet");
 }
}

The result looks just like the picker from other parts of SharePoint, with functioning look-ahead.


As promised, here is the markup if you want to configure your TaxonomyWebTaggingControl declaratively.


    0d8382cf-2d63-4421-a37a-b9386d0be5c8
    7adeced1-b73e-47dc-b695-82bb28e2f48f

Friday, April 23, 2010

SP 2010 Managed Metadata TermSet.CreateTerm Throws Error

I've been working on importing a set of Terms into my Managed Metadata Term Store from a 3rd party database. However, I ran into a snag. When I execute the following code, I get an error, "There is already a term with the same default label and parent term."

public static void CreateTermIfNotExists(TermSet termSet, string termName)
{
 if (termSet != null && !string.IsNullOrEmpty(termName))
 {
  Term term = null;

  //This throws an exception if the Term doesn't exist
  try
  {
   term = termSet.Terms[termName];
  }
  catch { }

  if (term == null)
  {
   Term t = termSet.CreateTerm(termName, 1033);
   termSet.TermStore.CommitAll();
  }
 }
}

I attached my debugger and found out that even though I was checking for my term, the code would say it wasn't there, even though it was! The problem, was that the specific term causing me problems had an ampersand. The term name I supplied was "Foo & Bar", but the value put into the TermStore actually contained unicode version of the ampersand.

Looking through the documentation, I found this relevant comment:

The name value will be normailized to trim consecutive spaces into one and replace the & character with the wide character version of the character (\uFF06). The leading and trailing spaces will be trimmed. It must be non-empty and cannot exceed 255 characters, and cannot contain any of the following characters ; "<>|&tab.

That was indeed the behavior I was seeing. Thinking I had just found a limitation of the TermSet.Terms collection, I changed my code to this:

public static void CreateTermIfNotExists(TermSet termSet, string termName)
{
 if (termSet != null && !string.IsNullOrEmpty(termName))
 {
  Term term = null;

  TermCollection tc = termSet.GetTerms(termName, 1033, true, StringMatchOption.ExactMatch, 1, false);
  if (tc != null && tc.Count > 0)
  {
   term = tc[0];
  }

  if (term == null)
  {
   Term t = termSet.CreateTerm(termName, 1033);
   termSet.TermStore.CommitAll();
  }
 }
}

I ran again, and this time instead of blowing up on just that one case, my code went crazy trying to insert a bunch of different terms that were working fine before and throwing way more exceptions. A little research on this method led me to this post, which suggests the TermSet.GetTerms method does not work in Beta 2, which seems to be what I just discovered as well.

I decided to explore the reference to normalizing term names from the MSDN link. My final pass at the code became:

public static void CreateTermIfNotExists(TermSet termSet, string termName)
{
    if (termSet != null && !string.IsNullOrEmpty(termName))
    {
        Term term = null;

        try
        { 
            string normalizedTermName = TermSet.NormalizeName(termName);
            term = termSet.Terms[normalizedTermName];
        }
        catch { }

        if (term == null)
        {
            Term t = termSet.CreateTerm(termName, 1033);
            termSet.TermStore.CommitAll();
        }
    }
} 

Finally, success! Moral of the story - when you query your TermSet for a specific Term you need to normalize your name first, because that is how it will be stored internally.