Easy ASP.NET Web API resource updates with Delta

Β· 985 words Β· 5 minutes to read

One of the great features of the Microsoft ASP.NET WebAPI OData package (which you can grab as prerelease from Nuget, and which will soon, in next release cycle, become part of Web API core) is a little dynamic proxy object called Delta.

It allows you to perform ridiculously easy mapping of properties between the model obtained from the database and the model passed by the client - thus facilitating all kinds of update scenarios your application may encounter.

Unfortunately, it will not work for you - unless you commit to ODataMediaTypeFormatter and all the extravaganza related to OData. What if you want to use the traditional API formatters, but still leverage on the power of Delta?

Let’s have a look at how you can perform really smooth full (PUT) and partial (PATCH) updates of your resources in Web API.

Introducing Delta πŸ”—

The idea behind Delta is that it acts as a lightweight dynamic proxy between your update source model (or view model or whatever you wish to update with) and the update target. As a consequence you’d use Delta as an input parameter for your resource’s update action (in place of just plain T).

It will track the changed properties and unmodified ones, making sure only the stuff that’s supposed to be updated, gets updated. Delta does that by generating lambda based property setters Action>TEntity,object< and use them to set the properties of your entity.

For example, if you have a CustomerController, your Web API action would have a PUT action that would no longer take a Customer object as an input, but rather a Delta.

Let’s write some code.

Adding Delta to your API πŸ”—

Let’s take a simple API as an example. A model:

public class Team {  
public Guid Id {get; set;}  
public string Name {get; set;}  
public string League {get; get;}  
public int Rating {get; set;}  
}  

With Delta, a “traditional” Web API controller, with typical CRUD resource operations would now look like this:

public class TeamController : ApiController  
{  
private static List<Team> _teams = new List<Team> { new Team {Id = Guid.NewGuid(), League = "NHL", Name = "Toronto Maple Leafs", Rating = 10 }}; 

public IEnumerable<Team> Get()  
{  
return _teams;  
}

public Team Get(Guid id)  
{  
return _teams.FirstOrDefault(x => x.Id == id);  
}

public void Post(Team value)  
{  
_teams.Add(value);  
}

[AcceptVerbs("Patch")]  
public void Patch(Guid id, Delta<Team> value)  
{  
var t = _teams.FirstOrDefault(x => x.Id == id);  
if (t == null) throw new HttpResponseException(HttpStatusCode.NotFound);

value.Patch(t);  
}

public void Put(Guid id, Delta<Team> value)  
{  
var t = _teams.FirstOrDefault(x => x.Id == id);  
if (t == null) throw new HttpResponseException(HttpStatusCode.NotFound);

value.Put(t);  
}

public void Delete(Guid id)  
{  
var t = _teams.FirstOrDefault(x => x.Id == id);  
if (t == null) throw new HttpResponseException(HttpStatusCode.NotFound);

_teams.Remove(t);  
}  
}  

With Delta we have improved the typical CRUD controller in two ways:
a) simplified the bit resposible for update (HTTP PUT)
b) introduced partial update (HTTP PATCH)

  • PUT /api/team/33909eaf-56a1-4467-a01a-64b94f10490c
{  
"Id": 33909eaf-56a1-4467-a01a-64b94f10490c,  
"Name": "Toronto Maple Leafs",  
"League": "NHL NorthEast",  
"Rating": 100  
}  

This will update the entire model.

As you see, in both cases Delta saves us a lot of effort of tracking which properties to update. The real power of it is visible with the use of HTTP PATCH, as it allows the client to pass incomplete objects, for example:

  • PATCH /api/team/33909eaf-56a1-4467-a01a-64b94f10490c
{  
"League": "NHL NorthEast"  
}  

This will partially update the model (only using the property passed by the client).

But hey, where does this Delta come from? πŸ”—

You could achieve this by just grabbing the Microsoft ASP.NET WebAPI OData from Nuget, and then enabling OData:

PM> Install-Package Microsoft.AspNet.WebApi.OData -Pre  
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();  
modelBuilder.EntitySet<Team>("team");  
var model = modelBuilder.GetEdmModel();  
GlobalConfiguration.Configuration.EnableOData(model);  

I will not go into details - since this is not a post about OData - but there are some reasons why you might not want to do that, and want to still use vanilla API with just JsonMediaTypeFormatter. If you choose to go with OData there are a number of other considerations, I recommend you follow Alex James’ great posts to find out more.

What you can do instead - is what we I did , and that is internalize the relevant classes responsible for Delta. I took out some of the existing framework code (5 classes) - from Microsoft ASP.NET WebAPI OData and its System.Web.Http.OData dll. I then removed the dependencies on other helper classes, so that we don’t have to drag along the entire OData stack.

An important thing to note here - the default implementation of Delta was intended to be used with ODataMediaTypeFormatter, since it uses specialized serializers/deserializers and when using it against i.e. the default JsonMediaTypeFormatter, based on JSON.NET it runs into some issues.

I noticed it would choke on:

  • int values - because Delta would use Action>TEntity,object< to provide a dynamic property setter. Since JSON.NET would arbitrarily deserialize int into Int64 (long) trying to use an object based (boxed value) lambda to set such value into an int property would throw an InvalidCastException because effectively you are trying to put a long into an int
  • Guid values - again, since they would come out of JSON.NET as string and the generic lambda setter would be trying to set a string into a Guid property

I added code to mitigate these errors. There might be more similar issues like this - this is a very experimental exercise after all (at some point, probably requires a nice map of types), but it ends up working nicely for the simple examples I’ve been running.

You can get all the required classes from this Gist, and simply copy them over to your solution. As mentioned, the code comes from ASP.NET Web API OData, just been slightly modified to remove external dependencies and fix some errors.

This way you can start using/experimenting with Delta with plain old JsonMediaTypeFormatter. Oh and one final thing, this is purely experimental!

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP