5

I am using HttpPatch to partially update an object. To get that working I am using Delta and Patch method from OData (mentioned here: What's the currently recommended way of performing partial updates with Web API?). Everything seems to be working fine but noticed that mapper is case sensitive; when the following object is passed the properties are getting updated values:

{
  "Title" : "New title goes here",
  "ShortDescription" : "New text goes here"
}

But when I pass the same object with lower or camel-case properties, Patch doesn't work - new value is not going through, so it looks like there is a problem with deserialisation and properties mapping, ie: "shortDescription" to "ShortDescription".

Is there a config section that will ignore case sensitivity using Patch?

FYI:

On output I have camel-case properties (following REST best practices) using the following formatter:

//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;

//sample output
{
  "title" : "First",
  "shortDescription" : "First post!"
}

My model classes however are follwing C#/.NET formatting conventions:

public class Entry {
  public string Title { get; set;}
  public string ShortDescription { get; set;}
  //rest of the code omitted
}
Community
  • 1
  • 1
303
  • 312
  • 4
  • 15

2 Answers2

7

Short answer, No there is no config option to undo the case sensitiveness (as far as i know)

Long answer: I had the same problem as you today, and this is how i worked around it.
I found it incredibly annoying that it had to be case sensitive, thus i decided to do away with the whole oData part, since it is a huge library that we are abusing....

An example of this implementation can be found at my github github

I decided to implement my own patch method, since that is the muscle that we are actually lacking. I created the following abstract class:

public abstract class MyModel
{
    public void Patch(Object u)
    {
        var props = from p in this.GetType().GetProperties()
                    let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
                    where attr == null
                    select p;
        foreach (var prop in props)
        {
            var val = prop.GetValue(this, null);
            if (val != null)
                prop.SetValue(u, val);
        }
    }
}

Then i make all my model classes inherit from *MyModel*. note the line where i use *let*, i will excplain that later. So now you can remove the Delta from you controller action, and just make it Entry again, as with the put method. e.g.

public IHttpActionResult PatchUser(int id, Entry newEntry)

You can still use the patch method the way you used to:

var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();

Now, let's get back to the line

let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))

I found it a security risk that just any property would be able to be updated with a patch request. For example, you might now want the an ID to be changeble by the patch. I created a custom attribute to decorate my properties with. the NotPatchable attribute:

public class NotPatchableAttribute : Attribute {}

You can use it just like any other attribute:

public class User : MyModel
{
    [NotPatchable]
    public int ID { get; set; }
    [NotPatchable]
    public bool Deleted { get; set; }
    public string FirstName { get; set; }
}

This in this call the Deleted and ID properties cannot be changed though the patch method.

I hope this solve it for you as well. Do not hesitate to leave a comment if you have any questions.

I added a screenshot of me inspecting the props in a new mvc 5 project. As you can see the Result view is populated with the Title and ShortDescription.

Example of inspecting the props

Community
  • 1
  • 1
rik.vanmechelen
  • 1,904
  • 17
  • 24
  • Hi Rik, thank you for your answer, I have added code, but there are some differences (using MVC 5 now) and it is not working: ` var props = from p in this.GetType().GetProperties() let attr = p.GetCustomAttributes(typeof(NotPatchableAttribute), false) where attr == null select p; ` However, that query returns nothing. – 303 Oct 30 '13 at 16:52
  • @303 Oh.. i am using web api 2, let me quickly test it on mvc 5 and i will get back to you.. nothing is insolvable :) – rik.vanmechelen Oct 31 '13 at 08:20
  • @303 I just tested it in a new mvc project, and it seems to work. for speeds sake, i just adapted the edit method to use the patch, and it works. I will put the code on github for you to compare. – rik.vanmechelen Oct 31 '13 at 09:21
  • FYI: I am using .Net 4.5, but getting this error: 'System.Reflection.PropertyInfo' does not contain a definition for 'GetCustomAttribute' and no extension method 'GetCustomAttribute' accepting a first argument of type 'System.Reflection.PropertyInfo' could be found (are you missing a using directive or an assembly reference?) Do I need extra reference? – 303 Oct 31 '13 at 12:59
  • 1
    @303 you need using System.Reflection; of course. I just cloned my example application again, created a new solution for it and reinstalled (uninstall and install again) the following nugget packaged: entity framework, asp.net mvc and asp.net web optimization framework. After this all my references were ok and i was able to build. When running the application it seems to work for me... – rik.vanmechelen Oct 31 '13 at 13:40
  • @303 did you get the error with my project? if not, try it, else i will have to be able to test it myself – rik.vanmechelen Oct 31 '13 at 13:41
  • Thanks Rik, got that working, however there are some problems. I will describe them on GitHub. I will mark your solution as an aswer. – 303 Oct 31 '13 at 15:42
  • Wouldn't be better to use Ready-Only existing attribute to mark properties as not updatable. Also if you expose BindingModels you do not have to worry about patching a 'non-updatable-field'. – Bart Calixto Jan 09 '14 at 21:53
  • those are valid options, but the main idea stays the same i think :) – rik.vanmechelen Jan 10 '14 at 08:01
  • Hey. I just tried this out and I have some concerns. If "MyModel" has a non-nullable property then this solution does not appear to work. For instance, if the saved instance of MyModel has int Index: 1 and string Title: "Foo" and I patch Title to "Bar" then it sets Index to default(int)... right? There doesn't appear to be a way to avoid that limitation without making all the properties nullable. Is that correct? – Sean Anderson Sep 25 '14 at 04:00
  • yes, this implementation is far from perfect :) iirc there are even more concerns with this implementation: e.g. if you actually want to set the value back to null. As a quick work around you could model bind to a JObject (if you are using the newton JSON nuget package). In that case you can be sure that all the values available in the JObject have to be set. – rik.vanmechelen Sep 25 '14 at 14:10
3

It can be done quite easily with a custom contract resolver that inherits CamelCasePropertyNamesContractResolver and implementing CreateContract method that look at concrete type for delta and gets the actual property name instead of using the one that comes from json. Abstract is below:

public class DeltaContractResolver : CamelCasePropertyNamesContractResolver
{
        protected override JsonContract CreateContract(Type objectType)
        {
            // This class special cases the JsonContract for just the Delta<T> class. All other types should function
            // as usual.
            if (objectType.IsGenericType &&
                objectType.GetGenericTypeDefinition() == typeof(Delta<>) &&
                objectType.GetGenericArguments().Length == 1)
            {
                var contract = CreateDynamicContract(objectType);
                contract.Properties.Clear();

                var underlyingContract = CreateObjectContract(objectType.GetGenericArguments()[0]);
                var underlyingProperties =
                    underlyingContract.CreatedType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (var property in underlyingContract.Properties)
                {
                    property.DeclaringType = objectType;
                    property.ValueProvider = new DynamicObjectValueProvider()
                    {
                        PropertyName = this.ResolveName(underlyingProperties, property.PropertyName),
                    };

                    contract.Properties.Add(property);
                }

                return contract;
            }

            return base.CreateContract(objectType);
        }

        private string ResolveName(PropertyInfo[] properties, string propertyName)
        {

            var prop = properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));

            if (prop != null)
            {
                return prop.Name;
            }

            return propertyName;
        }
}
Woland
  • 2,881
  • 2
  • 20
  • 33
  • 3
    This worked for me, but I was missing `DynamicObjectValueProvider`, I found the implementation [here](https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/DeltaJsonDeserialization/DeltaJsonDeserialization.Server/DeltaContractResolver.cs) alongside a DeltaContractResolver implementation that I couldn't get working – Joel Mitchell Nov 03 '14 at 11:33
  • 1
    In case anyone else is wondering, you set the contract resolver in WebApiConfig like this `config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DeltaContractResolver();` – Kim Mar 13 '17 at 15:23