How to PATCH in Web API and OData

gdoron is supporting Monica picture gdoron is supporting Monica · Sep 1, 2014 · Viewed 19.3k times · Source

From reading the RFC specification of the Patch verb it's clear that the Patch verb shouldn't get values to partially update the entity but operations to make:

...With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.

In MSDN for the Delta class it's also clear, as the Patch description says:

Overwrites the original entity with the changes tracked by this Delta.

Unlike the description of Put:

Overwrites the original entity with the values stored in this Delta.

So far so good, but I couldn't find a way to send those "instructions" with OData, No matter what I do, Delta.Patch only replaces the values.

What should be the syntax of Patch request?

The ways I tried were:

PATCH http://localhost:55783/Products(1) HTTP/1.1
User-Agent: Fiddler
Host: localhost:55783
Content-Length: 19
Content-type: application/json

{ "Price": 432 }

And

{ "op": "add", "path": "/Price", "value": 423432 }

And stuff near that.


Update:

Thanks to Michael Moore and from reading the whole Delta class with ILSpy I think it's indeed a bug in the Patch verb design.
I opened a bug for Microsoft, you can vote on it if you need it to be fixed too.

Answer

Michael picture Michael · Sep 2, 2014

I'm not sure what you're trying to achieve is possible. At least not with Delta<TEntity>.Patch(..)

Assuming that you have Product entity and somewhere in your PATCH action you have

[AcceptVerbs("PATCH")]
public void Patch(int productId, Delta<Product> product)
{
    var productFromDb = // get product from db by productId
    product.Patch(productFromDb);
    // some other stuff
}

When product is created, internally it calls Delta<TEntityType> constructor, which looks like this (parameterless constructor also makes call to this one, passing typeof(TEntityType)

public Delta(Type entityType)
{
    this.Initialize(entityType);
}

Initialize method looks like this

private void Initialize(Type entityType)
{
    // some argument validation, emitted for the sake of brevity 

    this._entity = (Activator.CreateInstance(entityType) as TEntityType);
    this._changedProperties = new HashSet<string>();
    this._entityType = entityType;
    this._propertiesThatExist = this.InitializePropertiesThatExist();
}

Interesting part here is this._propertiesThatExist which is a Dictionary<string, PropertyAccessor<TEntityType>> which holds properties of the Product type. PropertyAccessor<TEntityType> is internal type to allow easier manipulation of properties.

When you call product.Patch(productFromDb) this is what is happening under the hood

// some argument checks
PropertyAccessor<TEntityType>[] array = (
        from s in this.GetChangedPropertyNames()
        select this._propertiesThatExist[s]).ToArray<PropertyAccessor<TEntityType>>();

    PropertyAccessor<TEntityType>[] array2 = array;

    for (int i = 0; i < array2.Length; i++)
    {
        PropertyAccessor<TEntityType> propertyAccessor = array2[i];
        propertyAccessor.Copy(this._entity, original);
    }

As you can see it gets properties that were changed, iterates over them and sets values from instance that was passed to Patch action to the instance you get from db. So the operation you're passing, property name and value to add are not going to reflect anything.

propertyAccessor.Copy(this._entity, original) method's body

public void Copy(TEntityType from, TEntityType to)
{
    if (from == null)
    {
        throw Error.ArgumentNull("from");
    }
    if (to == null)
    {
        throw Error.ArgumentNull("to");
    }
    this.SetValue(to, this.GetValue(from));
}