How to PATCH in Web API and OData

20,116

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));
}
Share:
20,116
gdoron is supporting Monica
Author by

gdoron is supporting Monica

Doron Grinzaig

Updated on July 15, 2022

Comments

  • gdoron is supporting Monica
    gdoron is supporting Monica almost 2 years

    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.

  • gdoron is supporting Monica
    gdoron is supporting Monica almost 10 years
    Thanks for your answer! I had the feeling using reflector could be helpful here... So is this a bug? Because the spec and MSDN both talk about "instructions" not values. So what's the difference between Delta.Put and Delta.Patch? By the way, do you work at Microsoft or "only" an enthusiastic programmer?
  • Michael
    Michael almost 10 years
    @gdoron are you talking about MSDN for Delta's Patch method?
  • Michael
    Michael almost 10 years
    @gdoron you would probably be surprised, but Delta's PUT method doesn't differ that much, the only difference is that in PUT method it also goes thru the unchanged properties list and just copies those values also. And no, I don't work in MS :) Btw, just as a side note - I've used IlSpy not reflector :)
  • Michael
    Michael almost 10 years
    @gdoron as you mentioned in post - it just says it overwrites original entity with the changes tracked by Delta. You saw how Delta tracks changes, it doesn't look for instructions and nowhere in MSDN I could find any word about instructions. I agree with you thou, RFC spec talks about instructions.
  • gdoron is supporting Monica
    gdoron is supporting Monica almost 10 years
    So are you saying that Delta.Put doesn't partial update the entity but override the entity completely just like simple Http put method?!
  • Michael
    Michael almost 10 years
  • gdoron is supporting Monica
    gdoron is supporting Monica almost 10 years
  • Admin
    Admin about 8 years
    You can use [HttpPatch] instead of [AcceptVerbs("PATCH")].