Sitecore 8 WebApi cannot update field values

by John Nye

16 Jan
2015

I was recently playing around with Sitecore 8 with some colleagues and was looking into using the ItemWebApi to update a items field value. We grabbed the documentation and went about our implementation.

First things first, I enabled the ItemWebApi and set it to the least restrictive setting since I was only testing.

Sitecore.ItemWebApi.config

  <site name="website">
    <patch:attribute name="itemwebapi.mode">StandardSecurity</patch:attribute>
    <patch:attribute name="itemwebapi.access">ReadWrite</patch:attribute>
    <patch:attribute name="itemwebapi.allowanonymousaccess">true</patch:attribute>
  </site>

Next we created some javascript to send a PUT request to Sitecore with some hard-coded id's and values...

$.ajax({
    type: 'PUT',
    headers: { 'Content-Type': 'application/x-www-formurlencoded'},
    url: '/-/item/v1/?sc_itemid=9BDA4182-F418-4D85-8997-3ABEE4546641',
    data: { 'BF01288E-0CB1-44F4-8DD0-587C46374AFC': 'new field value' }
});

Having sent the request I get a response with the item that has been edited BUT... on investigating the values of the field I was editing the value had not changed!

The issue

After a day of digging we finally found the cause of the issue.

Having decompiled the pipeline method responsible for the update operations we located the following code that was updating the fields.

public override void Process(UpdateArgs arguments)
{
  Assert.ArgumentNotNull((object) arguments, "arguments");
  Item[] scope = arguments.Scope;
  NameValueCollection form = arguments.Context.HttpContext.Request.Form;
  foreach (Item obj in scope)
  {
    if (!(Context.Site.Name == "shell") || obj.Access.CanWriteLanguage())
    {
      obj.Fields.ReadAll();
      obj.Editing.BeginEdit();
      foreach (string fieldId in (NameObjectCollectionBase) form)
      {
        Field field = UpdateScope.GetField(obj, fieldId);
        if (field != null && UpdateScope.CanUpdateField(field, arguments.Context.Settings.Mode))
          field.Value = form[fieldId];
      }
      obj.Editing.EndEdit();
    }
  }
}

Ok, so the fields that we want to update are read from the HttpContext.Request.Form collection. On debugging this we found that the form was always empty... and as you'd expect the form collection is also readonly. So why is there nothing in the form collection? Well after bit more digging, StackOverflow came to the rescue. Sure enough, when you read the msdn documentation it clearly states:

The Form collection retrieves the values of form elements posted to the HTTP request body, with a form using the POST method.

Wait a minute, so Request.Form only populates the Form collection on POST requests...? And the collection is readonly so we can't add to this collection. How could this ever have worked?

At this point I was at a loss, but not wanting to give up we kept on pluggin away, creating our own pipeline process to help debug things. Eventually we came to a solution which horrifies me and SHOULD NOT BE USED IN A PRODUCTION ENVIRONMENT

public class WtfItemWebApi : UpdateScope
{
    public override void Process(UpdateArgs arguments)
    {
        //Get the request
        HttpRequest httpRequest = arguments.Context.HttpContext.Request;
        //Retrieve the data sent up with the put request
        var putData = new StreamReader(httpRequest.InputStream).ReadToEnd();
        //Split the data into itemId and value
        var parts = putData.Split('=');
        string itemId = parts[0];
        string value = HttpUtility.UrlDecode(parts[1]);

        //This method is naughty... very very naughty
        MakeFormEditable(arguments.Context.HttpContext);
        // add field id and value to the form collection
        httpRequest.Form.Set(itemId, value);

        // Continue on to the default update field pipeline class
        base.Process(arguments);
    }

    /// <summary>
    /// Sitecore use PUT requests to update items
    /// Sitecore use HttpRequest.Form to read new field values
    /// Microsoft HttpRequest.Form is populated on *POST* requests
    /// Use REFLECTION to open the collection and make it writeable
    /// </summary>
    /// <param name="httpContext"></param>
    protected void MakeFormEditable(HttpContext httpContext)
    {
        var collection = httpContext.Request.Form;

        var propInfo = collection.GetType()
                                 .GetProperty("IsReadOnly", 
                                              BindingFlags.Instance | BindingFlags.NonPublic);
        propInfo.SetValue(collection, false, new object[] { });
    }
}

Now I have my new update field pipeline class all I need to do is plug it in place of the old one.

Sitecore.ItemWebApi.config

  <itemWebApiUpdate>
    <processor type="MyNamespace.Web.Pipelines.WtfWebApi, MyNamespace.Web" />
    <processor type="Sitecore.ItemWebApi.Pipelines.Update.ReadUpdatedScope, Sitecore.ItemWebApi" />
  </itemWebApiUpdate>

And... TA DA!!!! My fields are now updating.

This was a really difficult bug to find and I'd love to hear from anyone else who has used the update field functionality on the ItemWebApi since documentation is rather thin on the ground, especially for Sitecore 8.

The solution above is a HACK so please do not use this in a production environment. It was only put in place in order to get to the bottom of this issue.

If you have used the ItemWebApi to update field values, both successfully or otherwise, I'd love to hear from you so please use the comments form below. Alternatively, if you would prefer, feel free to get in touch with me via twitter @ninjanye

Comments 0 * Be the first to comment!

Leave a message...

21 Sep
2017