HTTP PATCH support for OData in WCF Data Services

NOTE: this post was originally published on 5/20/10 but somehow disappeared, probably during the transition to the new blogging infrastructure in blogs.msdn.com. This version should be close to the original although it may differ in details.

Back when we were working on the initial version of OData (Astoria back then) there were some discussion about introducing a new HTTP method called PATCH, which would be similar to PUT but instead of having to send a whole new version of the resource you wanted to update, it would let you send a patch document with incremental changes only. We needed exactly that, but since the proposal was still in flux we decided to use a different word (to avoid any future name clash) until PATCH was ready. So OData services use a custom method called "MERGE" for now.

Now HTTP PATCH is official (see https://tools.ietf.org/html/rfc5789). We'll queue it up and get it integrated into OData and our .NET implementation WCF Data Services when we have an opportunity. Of course those things take a while to rev, and we have to find a spot in the queue, etc. In the meanwhile, here is an extremely small WCF behavior adds PATCH support to any service created with WCF Data Service. This example is built using .NET 4.0, but it should work in 3.5 SP1 as well.

https://code.msdn.microsoft.com/DataServicesPatch

To use it, just include the sample code in PatchBehavior.cs in your project and add [PatchSupportBehavior] as an attribute to your service class. For example:

    [PatchSupportBehavior]
    public class Northwind : DataService<NorthwindEntities>
    {
        // your data service code
    }

To see it working you can use any environment with an HTTP stack. Below is an example using curl to update a single attribute (the CategoryName value) using PATCH. The first try shows what happens when you try PATCH against a service that doesn't have PATCH enabled:

curl -v -X PATCH -H content-type:application/json -d "{CategoryName:'Beverages'}" https://localhost:29050/Northwind.svc/Categories(1)
* About to connect() to localhost port 29050 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 29050 (#0)
> PATCH /Northwind.svc/Categories(1) HTTP/1.1
> User-Agent: curl/7.19.3 (i386-pc-win32) libcurl/7.19.3 OpenSSL/0.9.8j zlib/1.2.3
> Host: localhost:29050
> Accept: */*
> content-type:application/json
> Content-Length: 26
>
< HTTP/1.1 501 Not Implemented
< Date: Mon, 14 Jun 2010 22:36:49 GMT
< DataServiceVersion: 1.0;
< Content-Length: 217
< Cache-Control: private
< Content-Type: application/xml
< Connection: Close
<
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code></code>
  <message xml:lang="en-US">Not Implemented</message>
</error>
* Closing connection #0

Once the PATCH support attribute is applied, things look better:

curl -v -X PATCH -H content-type:application/json -d "{CategoryName:'Beverages'}" https://localhost:29050/Northwind.svc/Categories(1)
* About to connect() to localhost port 29050 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 29050 (#0)
> PATCH /Northwind.svc/Categories(1) HTTP/1.1
> User-Agent: curl/7.19.3 (i386-pc-win32) libcurl/7.19.3 OpenSSL/0.9.8j zlib/1.2.3
> Host: localhost:29050
> Accept: */*
> content-type:application/json
> Content-Length: 26
>
< HTTP/1.1 204 No Content
< Date: Mon, 14 Jun 2010 22:34:42 GMT
< DataServiceVersion: 1.0;
< Cache-Control: no-cache
< Content-Length: 0
< Connection: Close
<
* Closing connection #0

As for how it works, the behavior catches all requests before they go to the WCF Data Services runtime. For each request it inspects the HTTP method used and if it is PATCH then it changes it to MERGE to make the system believe it was MERGE all along.

Usual disclaimers:
- I tested this by running it exactly once. You may want to be more detailed on your quality process :)
- Once we do the real thing the system may or may not behave as this one, so you need to be prepared to deal with some potential differences between the two.

-pablo