1

I am PATCHing JSON from the client to my Web API 2 controller.

The request looks like:

Request URL: http://localhost:28029/PlaylistItem/5df2b99f-e021-4c81-8ff5-a34c013470aa
Request Payload: { sequence: 5000 }

My controller's method looks like:

[Route("{id:guid}")]
[HttpPatch]
public void Patch(Guid id, Delta<PlaylistItemDto> playlistItemDto)
{
}

Where PlaylistItemDto looks like:

public class PlaylistItemDto
{
    public Guid PlaylistId { get; set; }
    public Guid Id { get; set; }
    public int Sequence { get; set; }
    ...
}

This successfully sends a request to the controller, but fails to work properly because of case-sensitivity. The OData library does not properly translate sequence into Sequence.

I found one thread regarding the issue, asp.net mvc web api partial update with OData Patch, but I found the solution to be lackluster. Is there no current working solution for this issue? Case-sensitivity would seem to be a very common use case for PATCHing JSON data from a client to a server.

Community
  • 1
  • 1
Sean Anderson
  • 27,963
  • 30
  • 126
  • 237
  • How did you end up solving it? The below answer seem to focus on JSON.net serializer, Odata uses a completly different serializer. I have the same issue now. I ended up fixing the payload as Odata is a standard and did not want to really change anything that is not really expected. – R4nc1d Mar 14 '19 at 20:27

1 Answers1

6

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
  • 1
    As stated in a comment on [another answer](http://stackoverflow.com/a/26390875/3153712), `DynamicObjectValueProvider` can be found [here](https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/DeltaJsonDeserialization/DeltaJsonDeserialization.Server/DeltaContractResolver.cs) and is set in WebApiConfig like this `config.Formatters.JsonFormatter.SerializerSettings.ContractR‌​esolver = new DeltaContractResolver();` – Kim Mar 13 '17 at 15:28