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!!
No comments:
Post a Comment