Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to PATCH in Web API and OData

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.

like image 815
gdoron is supporting Monica Avatar asked Sep 01 '14 14:09

gdoron is supporting Monica


1 Answers

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));
}
like image 141
Michael Avatar answered Sep 23 '22 08:09

Michael