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
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
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
- 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
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