WebAPI OData GraphDelta

In the course of a recent project I ran into a problem with the standard Delta<T> included in Microsoft WebAPI's OData functionality: it treats complex properties as atomic values meaning that instead of applying the delta to the existing value of the complex property it will create a new, blank value, apply the delta to that and then set the complex property to the result.

I asked about this behaviour on StackOverflow and it turns out that it's a known issue. This post briefly outlines how I implemented an alternative to Delta<T> that handled changes to complex types the same way as changes are handled for entities. The post will feature minimal code but source code is available on GitHub.

An example

Before looking at creating an alternative to Delta<T> we should first illustrate the problem.

Imagine we have a simple model with a Person entity and an Address complex type. The following JSON is used to update a Person using the MERGE or PATCH verbs:

{
  "Title": "Dr",
  "Address": {
    "Street": "Acacia Avenue"
  }
}

When then resulting Delta<T> is applied using the Patch method the effect will be the equivalent of:

person.Title = "Dr";
person.Address = new Address() { Street = "Acacia Avenue" };

We would rather have the address changes applied to the existing Address if there is one (only creating a new value if there is not):

person.Title = "Dr";

if(person.Address == null) {
  person.Address = new Address();
}

person.Address.Street = "Acacia Avenue";

GraphDelta<T>

Instead of creating an alternative delta type from scratch I created two new types, GraphDelta<T> and TypedGraphDelta, which use Delta<T> and its base class, TypedDelta internally to produce the functionality we want.

Much like Delta<T>, GraphDelta<T> is simply a generic shortcut type that inherits from TypedGraphDelta.

The constructor of TypedGraphDelta accepts a JSON.Net JObject representing the delta and a model Type. It then uses the properties of the type to read updated values using JSONPath and stores them in a root level TypedDelta instance. Any model property which is not a "simple" type (in this implementation value types, strings and byte arrays) is considered a child and is given its own TypedGraphDelta (and so the process is recursive and covers the entire object graph).

When Patch is called the root level TypedDelta is applied followed by all child TypedGraphDelta instances. If a property has a null value and changes were included in the JSON then a new instance will be created, populated and set.

You can see the full code for TypedGraphDelta here, and the tests are available here.

Note that this is a minimal implementation that does just enough to meet the basic requirements - e.g. it does not handle collections as how item matching is performed will likely be specific to your services.

Using GraphDelta<T>

In order for WebAPI to be able to correctly deserialise the JSON in MERGE/PATCH requests to GraphDelta<T> we need to create a specific formatter. The formatter is configured such that it can only be used to read GraphDelta<T> with media type application/json, and it simply reads the request stream into a JObject and then passes it to the constructor of GraphDelta<T>. You can see the full code for the formatter here.

We can then register this formatter with WebAPI:

config.Formatters.Add(new GraphDeltaFormatter());

Now our controller methods for MERGE/PATCH can accept GraphDelta<T>, and complex types can be updated in the same way as entities using the Patch method:

[AcceptVerbs("MERGE,PATCH")]
public IHttpActionResult Patch([FromODataUri] key, GraphDelta<T> delta) {
    //Validate the request, get the entity and call delta.Patch
}

Conclusion

In this post I have briefly summarised how I created an alternative to WebAPI OData's Delta<T> type that applies updates to complex types in the same as to entities and how to use it. The full source code is available on GitHub.

Comments