Reputation: 29311
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.
Upvotes: 1
Views: 973
Reputation: 2919
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;
}
}
Upvotes: 6