Wednesday, August 22, 2012

Managed Metadata, Lists, and the TaxonomyField

My client has several document libraries in many different Site Collections.  All of the site collections use a global Managed Metadata Service.

My client very much likes how lists can be filtered by the Managed Metadata Navigation on SharePoint 2010 Lists.  So, with all of those document libraries scattered all over the place, and the ease with which we can use Managed Metadata Navigation, why not create a list in the root site of the portal site collection that has links to ALL of the documents in ALL of the Site Collections???  You can do that right????  Sure, I say, sounds like fun.  Woof.

I am not a fan of duplicating data, especially when that data is potentially very large documents.  However my client simply wanted a link back to the original document and the ability to filter the documents just on the Global Managed Metadata...  So, what to do?  Essentially we create a list that has two fields, besides the title, a hyperlink field and a Managed Metadata field. Great!!  That's easy.  Now we need to populate it...  Boooo!!!  That's not so easy.
Well...  I will qualify that.  It wasn't easy until I finally got my head around just what the Taxonomy Fields were, how the Managed Metadata service worked, and all of the gotchas that surround it.

My solution was to create an Event Receiver that would look at document libraries, and if the list's ID matched one in a configuration list, an item would be created in the Master List that had a link to the document on the source list and a managed metadata field that corresponded with the term in the source list. Getting the URL to the document was easy. The tough part came with the Manged Metadata field.

First, because this was a big part of my problems, you have to get your head around the fact that the Managed Metadata fields are VERY different from any other SPField object you have ever worked with.  It is essential to look at them as completely separate entities from the normal SPFields out there.  Your first clue to this is that you can't just start declaring things as TaxonomyFields, that is what the SPField.Type is that contains the Managed Metadata stuff...  For one, TaxonomyFields do not reside in the normal Microsoft.SharePoint namespace, like the SPField objects do.  You need to create a reference to the Microsoft.SharePoint.Taxonomy.dll.  That is where those guys live.

Next, when you are typically using the object model to set a list item equal to another list item, it is pretty elementary.  You have your SPListItem, you use the handy dandy index to set your field, you do it for the other item as well and you set them equal to one another.  Easy peasy.
SPListItem.Items["Goof"] = SPListItem.Items["Ball"]

Sometimes the list items get a little bit more complex, and you have to use a SPFieldValue object to populate them.  Like a SPFieldUrl field.  You set it using the SPFieldUrlValue object.
SPFieldUrlValue.Url = "http://ramblingsofamadcomputerguy.blogspot.com/"
SPFieldUrlValue.Description = "Some Jerk's Blog"
SPListItem.Items["HyperLink"] = SPFieldUrlValue

In both cases you are simply setting the value with some strings that are associated with that SPField's type.  This is NOT the case when you are dealing with Managed Metadata.   Microsoft wanted to make things extra specially vague by adding things to site collections such as hidden text fields hidden lists.  And then requiring that you know these hidden things before you can set values of anything.  Way to go Microsoft.  Imma yell at you at the next SharePoint Conference ...  Not that you will care, but it will make me feel a whole lot better.

So we come to our first and most serious gotcha.  Metadata terms, like nearly all of SharePoint's things, all have a GUID.  In Managed Metadata terms this is called the SSPID.  These are easy to find, and actually can be brought up from your TaxonomyField object.  You would think that this, Global Unique IDentity , along with a friendly name, would be all that you need.  After all the ID is GLOBAL!!!!  SharePoint could just look it up in the store.  Right??  Right?????   Nope.  You also need something called a WSSID.
A what now??  Just as it implies the WSSID is an ID unique to the site collection with with the term is being used.  See where the gotcha is coming?
What if the term has never been used in the Site Collection?  How are we supposed to know what the WSSID is if it does not yet exist???  Bingo.  The million dollar question.

So this is what you need to set your TaxonomyField correctly.  A term name, a term GUID, and a WSSID.  Great.  All is not completely lost, our friends at Microsoft have given us away out of the woods.

So, there are a few things that we need to do.  First, we need a TaxonomyField set to the TaxonomyField in our list.  Notice something very critical here.  It is called a TaxonomyFIELD.  Because it IS a specific type of SPField object.  So when you are instantiated your TaxonomyField from a list you must use the FIELD object in the list. Lets look at some code. First we instantiated the TaxonomyFields from the list


SPField list1Field = item.Fields["Source"];
 SPListItem itemToAdd = list2.Items.Add();
 itemToAdd["Title"] = "Source";
 TaxonomyField list1TaxField = (TaxonomyField)item.Fields["Source"];
 TaxonomyField list2TaxField = (TaxonomyField)itemToAdd.Fields["Target"];


In the code you can see that the TaxonomyFields are instantiated by using the Fields collection of the SPListItem, NOT from setting it equal to the the VALUE of the item. This is important. You are instantiated a FIELD. We will set the value of this field later on.

Next we need to create the very thing that will populate the field we just instantiated. It is called a TaxonomyFieldValue object. Remember that C# is a strongly typed language, so if you are setting the TaxonomyFieldValue object equal to a value that you already have, you do need to cast objects correctly.
After you have the TaxonomyFieldValue object, we need to create a new one for the list item in our target list. This is where a second gotcha comes in. You can't just cast the source value as TaxonomyFieldValue and set it equal to the new TaxonomyFieldValue. Why? Because the values are NOT the same. For one, if you are crossing Site Collection boundaries, you won't have the same WSSID. Thought I wouldn't get to that didn't you??? There are some other things wrong, but rest assured setting them equal isn't the way to go.

Now I have been focusing a lot on what if the term hasn't been used before. What if it has been used, or if you are coping to another list in the same site collection. How would I get the WSSID? I'll go over this quickly, because you really don't want to be messing too much with where you get the WSSID, because there are much easier ways to go about it.

First the WSSID is a numerical field that represents the term in a very secret list. It is called the TaxonomyHiddenList. Sounds spooky right? You can look up the term in that list, just as you would any other SharePoint list. You take the ID of the SPListItem that holds the term and you have the WSSID. NEVER MESS WITH THE TaxonomyHiddenList!!!!!!!!!!! It will seriously ruin you day. Just stay away from it. It is one of those things behind the scenes that makes SharePoint work. Just don't go looking for it. It is bad news. Instead, use the API that comes with the Microsoft.SharePoint.Taxonomy namespace.

You can use the TaxonomyField.GetWssIdsOfTerm method to look up the WSSID in the TaxonomyHiddenList, without actually writing code to directly access the TaxonomyHiddenList. But if you use that method, you will need to write logic to do something else if the term does not exist. There is an easier way. It works in all situations and is elementary to use. It is the TaxonomyFieldValue.PopulateFromLabelGuidPair method.

The TaxonomyFieldValue.PopulateFromLabelGuidPair method will add the the term to the TaxonomyHiddenList if it does not exist, or it will automatically populate your TaxonomyFieldValue object with the proper WSSID if the term does exist. It is a very handy tool. It does everything you want, nothing you don't. Use this guy!!!
Now, you could set the WSSID to -1 and that will work similarly to the TaxonomyFieldValue.PopulateFromLabelGuidPair method, but... I don't like that as much. Microsoft is reluctant to change the way their methods work for backwards compatiability reasons, but seem to have no problem messing with how it uses "reserved" values such as -1. I think that the TaxonomyFieldValue.PopulateFromLabelGuidPair method is the best way to go.

Ok, all of that said, what does the TaxonomyFieldValue.PopulateFromLabelGuidPair use? It uses a delimited string of the friendly term name and the term GUID. The dilimter is the pipe "|".

I typically use the TaxonomyFieldValue.Label and the TaxonomyFieldValue.TermGuid properties put together with string.Format. It makes it very easy.

After that we have everything we need to set the value of our TaxonomyField. We use the TaxonomyField.SetFieldValue method and pass in the SPListItem that contains our TaxonomyFiled along with the TaxonomyFieldValue. Update the SPListItem and we are done!!!

//Set the TaxonomyFieldValue with the value of the first list item
TaxonomyFieldValue list1TaxFieldValue = (TaxonomyFieldValue)item["Source"];

//Create a new TaxonomyFieldValue with the TaxonomyField in the target list
TaxonomyFieldValue value = new TaxonomyFieldValue(list2TaxField);

/*Create the WSSID using the values for the term name and its GUID.  Notice that the values are seperated by the pipe.*/
value.PopulateFromLabelGuidPair(string.Format("{0}|{1}", list1TaxFieldValue.Label, list1TaxFieldValue.TermGuid));

//Use the SetFieldValue method with the SPListItem and the TaxonomyFieldValue
list2TaxField.SetFieldValue(itemToAdd, value);

//Update the SPListItem
itemToAdd.Update();



Not very straight forward, but not excessively difficult either. I don't know if this is changed in SharePoint 2013, but I would expect that it isn't. Microsoft, reportedly, makes much greater use of Managed Metadata in SharePoint 2013, so it would be a good idea to familiarize yourself with it.

Thanks to Nick Hobbs. His blog post showed me the way!!

Wednesday, August 1, 2012

MCPD SharePoint 2010!!

As you can see to the right, I passed exam 70-576 PRO: Designing and Developing Microsoft SharePoint 2010 Applications.  With that I have earned a Microsoft Certified Professional Developer certification.

Microsoft has said that the PRO tests are supposed to be the ones that are the most difficult, and for 70-668 Microsoft SharePoint 2010 Administrator I definitely agree, but this one, in my opinion, really wasn't that tough.  I had a difficult time with 70-573CS Microsoft SharePoint 2010, Applications Development.  Mainly because that one focused on the massive SharePoint API.  I don't really understand why Microsoft wants to test on minute aspects of the API though.  Most of the minutia you will get out of Intelisense, so why test over it?  The App Dev test should focus on core concepts, and the high level classes that need to be used and referenced.
The  Designing test, I think, did a good job of covering all of the bases, but I think more time, and questions should have been spent on, when to use the Property Bag, web.config, Hierarchical Object Store, and lists for configuration settings.  Also more time could have been spent on classes that are and are not available in Sandboxed solutions.  I might have thrown in a question or two on how to deploy sandboxed solutions while getting around some of the limitations of sandboxed solutions.
Inexplicably, time was spent on external BLOB storage.  I would have thought that this would have been a topic for the administration tests rather than the App Dev ones.  Sure, you may have to build your own provider using the ISPExternalBinaryProvider, but how often is that going to come up?  Stratigies to deal with sandboxed solution limitations are much more likely to be addressed by developers than deciding if External BLOBs are going to be used.  That is much more an Admin issue.

Anyway, the tests are in the books, and now I have to decide if I want to go a head and peruse a Microsoft Certified Master in SharePoint.  I really want to, but I don't know if I have the time and money to do so.  Also, what will it gain me?  I really don't want to be an independent traveling consultant, so why do I need the cert?  The boot camp that you have to attend is in excess of $18K, not counting travel expenses, they are held on the Microsoft Redmond campus, and are THREE WEEKS long, so why??  Just to have the initials after my name?  I have to admit that I do want the recognition...  However there are downsides to having such an advanced certification level.  A job might be willing to look at me because I have both MCITP and MCPD certs, but might think I am overqualified if I have the MCM.  Also if I apply for a leadership or manager position, the MCM might make a potential employer believe that I would be TOO involved in the day to day tech side of things, and neglect the manager work that I was hired to do.
If, perhaps, a company wanted to pay for this training I would do it, but right now...  No, I don't think so.  The cost is too high, and the benefit too small.  For now, I am content with my existing certification level.  It won't be long until I have to start boning up for the next series of exams on SharePoint 2013.