Monthly Archives: June 2015

Partial Updates with HTTP PATCH using ServiceStack.net and the JSON Patch format (RFC 6902)

I have been looking into implementing partial updates using the HTTP PATCH method using ServiceStack.net and the JSON Patch format (RFC 6902)

This is of interest since many updates do not neatly match the PUT method, which often is used for full entity updates (all properties). PATCH is intended to do one or more partial updates. There are a few blogs describing the use cases.

I’ve been happy using ServiceStack the way it was designed – RESTful, simple, using Message Based designs.

I could implement PATCH using my own message format – that is easy to do. Usually it would be the actual DTO properties, plus a list of fields which are actually going to be updated. You wouldn’t update all fields, and you don’t want to only update non-null properties, since sometimes “null” is a valid value for a property (it would be impossible to set a property to null from non-null).

In my opinion, using JSON Patch for the Request body has pros and cons.
Pros:

  • is an official RFC
  • covers a lot of use cases

Cons:

  • very generic, so we lose some of the benefit of strong typing
  • doesn’t have a slot for the Id of a resource when calling PATCH /employees/{Id}
    • doing this the “JSON Patch way” would be { “op”: “replace”, “path”: “/employees/123/title”, “value”: “Administrative Assistant” } , but that wastes the value of having it on the routing path.

JSON Patch supports a handful of operations: “add”, “remove”, “replace”, “move”, “copy”, “test”. I will focus on the simple “replace” op, since it easily maps to replacing a property on a DTO (or field in a table record).

The canonical example looks like this:

PATCH /my/data HTTP/1.1
Host: example.org
Content-Length: 55
Content-Type: application/json-patch+json
If-Match: "abc123"

[
    { "op": "replace", "path": "/a/b/c", "value": 42 }
]

I’m going to ignore the If-Match: / ETag: headers for now. Those will be useful if you want to tell the server to only apply your changes if the resource still matches your “If-Match” header (no changes in the meantime). “That exercise is left to the reader.”

Let’s say we have a more practical example:

  • an Employee class, backed by an [Employee] table, accessed by OrmLite
  • an EmployeeService class, implementing the PATCH method
  • the Request DTO to the Patch() method aligns to the JSON Patch structure

The Employee class would simply look like this (with routing for basic CRUD):

[Route("/employees", "GET,POST")]
[Route("/employees/{Id}", "GET,PUT")]
public class Employee
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Title { get; set; }
    public int? CubicleNo { get; set; }
    public DateTime StartDate { get; set; }
    public float Longitude { get; set; }
    public float Latitude { get; set; }
}

Now the shape of JSON Patch replace ops would look like this:

PATCH /employees/123 HTTP/1.1
Host: example.org
Content-Type: application/json

[
    { "op": "replace", "path": "/title", "value": "Junior Developer" },
    { "op": "replace", "path": "/cubicleno", "value": 23 },
    { "op": "replace", "path": "/startdate", "value": "2013-06-02T09:34:29-04:00" }
]

The path is the property name in this case, and the value is what to update to.

And yes, i also know i am sending Content-Type: application/json instead of Content-Type: application/json-patch+json . We’ll have to get into custom content type support later too.

Now, sending a generic data structure as the Request DTO to a specific resource ID doesn’t cleanly map to the ServiceStack style, because:

  • each Request DTO should be a unique class and route
  • there is not a field in the Request for the ID of the entity

The simple way to map the JSON to a C# class would define an “op” element class, and have a List<T> of them, like so:

public class JsonPatchElement
{
    public string op { get; set; } // "add", "remove", "replace", "move", "copy" or "test"
    public string path { get; set; }
    public string value { get; set; }
}

We create a unique Request DTO so we can route to the Patch() service method.

[Route("/employees/{Id}", "PATCH")]
public class EmployeePatch : List<JsonPatchElement>
{
}

But how do we get the #$%&& Id from the route?? This code throws RequestBindingException! But i can’t change the shape of the PATCH request body from a JSON array [].
The answer was staring me in the face: just add it to the DTO class definition, and ServiceStack will map to it. I was forgetting the C# class doesn’t have to be the same shape as the JSON.

[Route("/employees/{Id}", "PATCH")]
public class EmployeePatch : List<JsonPatchElement>
{
    public long Id { get; set; }
}

Think of this class as a List<T> with an additional Id property.

When the method is called, the JSON Patch array is mapped and the Id is copied from the route {Id}.

public object Patch(EmployeePatch dto)
{
    // dto.Id == 123
    // dto[0].path == "/title"
    // dto[0].value == "Joe"
    // dto[1].path == "/cubicleno"
    // dto[1].value == "23"

The only wrinkle is all the JSON values come in as C# string, even if they are numeric or Date types. At least you will know the strong typing from your C# class, so you know what to convert to.

My full Patch() method is below– note the partial update code uses reflection to update properties of the same name, and does primitive type checking for parsing the string values from the request DTO.

public object Patch(EmployeePatch dto)
{
    // partial updates

    // get from persistent data store by id from routing path
    var emp = Repository.GetById(dto.Id);

    if (emp != null)
    {
        // read from request dto properties
        var properties = emp.GetType().GetProperties();

        // update values which are specified to update only
        foreach (var op in dto)
        {
            string fieldName = op.path.Replace("/", "").ToLower(); // assume leading /slash only for example

            // patch field is in type
            if (properties.ToList().Where(x => x.Name.ToLower() == fieldName).Count() > 0)
            {
                var persistentProperty = properties.ToList().Where(x => x.Name.ToLower() == fieldName).First();

                // update property on persistent object
                // i'm sure this can be improved, but you get the idea...
                if (persistentProperty.PropertyType == typeof(string))
                {
                    persistentProperty.SetValue(emp, op.value, null);
                }
                else if (persistentProperty.PropertyType == typeof(int))
                {
                    int valInt = 0;
                    if (Int32.TryParse(op.value, out valInt))
                    {
                        persistentProperty.SetValue(emp, valInt, null);
                    }
                }
                else if (persistentProperty.PropertyType == typeof(int?))
                {
                    int valInt = 0;
                    if (op.value == null)
                    {
                        persistentProperty.SetValue(emp, null, null);
                    }
                    else if (Int32.TryParse(op.value, out valInt))
                    {
                        persistentProperty.SetValue(emp, valInt, null);
                    }
                }
                else if (persistentProperty.PropertyType == typeof(DateTime))
                {
                    DateTime valDt = default(DateTime);
                    if (DateTime.TryParse(op.value, out valDt))
                    {
                        persistentProperty.SetValue(emp, valDt, null);
                    }
                }

            }
        }

        // update
        Repository.Store(emp);

    }

    // return HTTP Code and Location: header for the new resource
    // 204 No Content; The request was processed successfully, but no response body is needed.
    return new HttpResult()
    {
        StatusCode = HttpStatusCode.NoContent,
        Location = base.Request.AbsoluteUri,
        Headers = {
            // allow jquery ajax in firefox to read the Location header - CORS
            { "Access-Control-Expose-Headers", "Location" },
        }
    };
}

For an example of calling this from the strongly-typed ServiceStack rest client, my integration test looks like this:

[Fact]
public void Test_PATCH_PASS()
{
    var restClient = new JsonServiceClient(serviceUrl);

    // dummy data
    var newemp1 = new Employee()
    {
        Id = 123,
        Name = "Kimo",
        StartDate = new DateTime(2015, 7, 2),
        CubicleNo = 4234,
        Email = "test1@example.com",
    };
    restClient.Post<object>("/employees", newemp1);

    var emps = restClient.Get<List<Employee>>("/employees");

    var emp = emps.First();

    var empPatch = new Operations.EmployeePatch();
    empPatch.Add(new Operations.JsonPatchElement()
    {
        op = "replace",
        path = "/title",
        value = "Kahuna Laau Lapaau",
    });

    empPatch.Add(new Operations.JsonPatchElement()
    {
        op = "replace",
        path = "/cubicleno",
        value = "32",
    });

    restClient.Patch<object>(string.Format("/employees/{0}", emp.Id), empPatch);

    var empAfterPatch = restClient.Get<Employee>(string.Format("/employees/{0}", emp.Id));

    Assert.NotNull(empAfterPatch);
    // patched
    Assert.Equal("Kahuna Laau Lapaau", empAfterPatch.Title);
    Assert.Equal("32", empAfterPatch.CubicleNo.ToString());
    // unpatched
    Assert.Equal("test1@example.com", empAfterPatch.Email);
}

I am uploading this code to github a full working Visual Studio 2013 project, including xUnit.net tests.

I hope this has been useful to demonstrate the flexibility of using ServiceStack and C# to implement the HTTP PATCH method using JSON Patch (RFC 6902) over the wire.

Update: i refactored the code so that any object can have it’s properties “patched” from a JsonPatchRequest DTO by using an extension method populateFromJsonPatch().

public object Patch(EmployeePatch dto)
{
    // partial updates
    // get from persistent data store by id from routing path
    var emp = Repository.GetById(dto.Id);

    if (emp != null)
    {
        // update values which are specified to update only
        emp.populateFromJsonPatch(dto);

        // update
        Repository.Store(emp);