Fixing ‘The SPListItem being updated was not retrieved with all taxonomy fields’ or how to provision taxonomy fields correctly

If someone wants to provision a taxonomy field in a declarative way, the following pattern should be followed:

      <Field Type="Note" DisplayName="Strategy_0" StaticName="Strategy_0" Name="Strategy_0" ID="{the GUID of your hidden NOTE field here}" ShowInViewForms="FALSE" Required="FALSE" Hidden="TRUE" />
      <Field Type="TaxonomyFieldType" DisplayName="Strategy" ShowField="Term1033" Required="FALSE" EnforceUniqueValues="FALSE" ID="{6f1ce2c9-9c40-407f-a0b9-7785220a14ff}" StaticName="Strategy" Name="Strategy">
        <Customization>
          <ArrayOfProperty>
            <Property>
              <Name>TermSetId</Name>
              <Value xmlns:q2="http://www.w3.org/2001/XMLSchema" p4:type="q2:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{the GUID of your termset here}</Value>
            </Property>
            <Property>
              <Name>AnchorId</Name>
              <Value xmlns:q3="http://www.w3.org/2001/XMLSchema" p4:type="q3:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">00000000-0000-0000-0000-000000000000</Value>
            </Property>
            <Property>
              <Name>TargetTemplate</Name>
              <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance"></Value>
            </Property>
            <Property>
              <Name>TextField</Name>
              <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{the GUID of your hidden NOTE field here}</Value>
            </Property>
            <Property>
              <Name>IsPathRendered</Name>
              <Value xmlns:q7="http://www.w3.org/2001/XMLSchema" p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
            </Property>
          </ArrayOfProperty>
        </Customization>
      </Field>

That would be sufficient, if it were not for the SspId property (the GUID of your keyword term store). Unfortunately there is no token for this and SharePoint is not smart enough to use the default one, if you leave it empty! You will get an error ‘The SPListItem being updated was not retrieved with all taxonomy fields‘. So, a little code work-around is needed.

You have to create a feature receiver or something else, which should run the following code against your field:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            var web = properties.Feature.Parent as SPWeb;

            // Strategy
            var strategyField = web.Lists["YourList"].Fields["YourTaxonomyField"] as TaxonomyField;
            TaxonomyHelper.ConnectTaxonomyField(web.Site, strategyField);
        }
        /// <summary>
        /// Fix up a taxonomy field
        /// </summary>
        /// <param name="site"></param>
        /// <param name="field"></param>
        public static void ConnectTaxonomyField(SPSite site, TaxonomyField field)
        {
            //Create a Taxonomy Session
            TaxonomySession session = new TaxonomySession(site);
            if (session.DefaultKeywordsTermStore != null)
            {
                field.SspId = session.DefaultKeywordsTermStore.Id;
                field.Update();
            }
            else
            {
                throw new Exception(string.Format("DefaultKeyWordTermStore not found in this site {0}", site.Url));
            }
        }

Again, if SharePoint used the default keyword store if you leave out the SspId empty or if there was a token for it, that would not be necessary.

Still, I am happy with the approach, as it is 95% declarative and I need the code only to link my field to the keyword store. The TermsetId is part of my declaration, and not the code, so, goal achieved!

When using [TaxonomyField].SetFieldValue you get ‘set AllowUnsafeUpdates to true’ exception

In some situations (such as ajax requests and other non-postback methodologies), you might encounter the following error:

Updates are currently disallowed on GET requests.  To allow updates on a GET, set the ‘AllowUnsafeUpdates’ property on SPWeb

This might happen if you are trying to set a Term type of object in an SPList item’s field. e.g.:

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

If you execute this in the backend during an ajax request (or maybe some other use cases too) this will fail with the exception mentioned above.

If you are reading this article it means that  you have already set the SPWeb’s AllowUnsafeUpdates to true and the problem still persists. For some reason this is the default SharePoint behavior and it doesn’t matter how many times you set the AllowUnsafeUpdates to true, it will still be ignored. So, we need another solution.

You might be tempted to use a solution (1, 2) such as setting a TaxonomyFieldValue to the field, instead of Term directly. Such as:

string termString = String.Concat(thisYear.GetDefaultLabel(1033), TaxonomyField.TaxonomyGuidLabelDelimiter, thisYear.Id);

TaxonomyFieldValue tagValue = new TaxonomyFieldValue(string.Empty);
tagValue.PopulateFromLabelGuidPair(termString);
oField.SetFieldValue(i, tagValue);

 

That would be a mistake because such approach breaks the Search Crawl (i.e. the crawler won’t crawl your field properly). So what is the correct solution? As crazy as it might sound, it is to set the HttpContext.Current to null just prior to setting the term field and then restoring it back.

So, solution:

var httpContext = HttpContext.Current;

HttpContext.Current = null;

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

HttpContext.Current = httpContext;

 

What does this achieve? It lies to SharePoint that you don’t have a HttpContext (i.e. you are not triggering this code from the browser), so it won’t validate your context and won’t throw an exception about AllowUnsafeUpdates.

 

Best Regards,

Hristo