Trying to make use of System.Web.Http.OData.Delta to implement PATCH methods in ASP.NET Web API services, but it seems unable to apply changes to properties of type IEnumerable<T>
. I'm using the latest Git revision of Delta (2012.2-rc-76-g8a73abe). Has anyone been able to make this work?
Consider this data type, which it should be possible to update in a PATCH request to the Web API service:
public class Person
{
HashSet<int> _friends = new HashSet<int>();
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<int> Friends
{
get { return _friends; }
set
{
_friends = value != null ? new HashSet<int>(value) : new HashSet<int>();
}
}
public Person(int id, string firstName, string lastName)
{
Id = id;
FirstName = firstName;
LastName = lastName;
}
public Person()
{
}
}
This Web API method implements patching of a Person through Delta<Person>
:
public void Patch(int id, Delta<Person> delta)
{
var person = _persons.Single(p => p.Id == id);
delta.Patch(person);
}
If I send a PATCH request with the following JSON to the service, the person's Friends
property should be updated, but alas it doesn't happen:
{"Friends": [1]}
The crux of the matter is really how to make Delta update Friends
with this data. See also the discussion at CodePlex.
The problem likely is that Deta will try to assign JSON's JArray
to your Hashset<int>
If you are using it against JsonMEdiaTypeFormatter and you internalized the Delta code (meaning you can modify it), you'd have to do something like this (this is rough, but works):
Inside, bool TrySetPropertyValue(string name, object value)
of Delta<T>
, where it returns false:
if (value != null && !cacheHit.Property.PropertyType.IsPrimitive && !isGuid && !cacheHit.Property.PropertyType.IsAssignableFrom(value.GetType()))
{
return false;
}
Change to:
var valueType = value.GetType();
var propertyType = cacheHit.Property.PropertyType;
if (value != null && !propertyType.IsPrimitive && !propertyType.IsAssignableFrom(valueType))
{
var array = value as JArray;
if (array == null)
return false;
var underlyingType = propertyType.GetGenericArguments().FirstOrDefault() ??
propertyType.GetElementType();
if (underlyingType == typeof(string))
{
var a = array.ToObject<IEnumerable<string>>();
value = Activator.CreateInstance(propertyType, a);
}
else if (underlyingType == typeof(int))
{
var a = array.ToObject<IEnumerable<int>>();
value = Activator.CreateInstance(propertyType, a);
}
else
return false;
}
This will only work with collections of int
or string
but hopefully nudges you into a good direction.
For example, now your model can have:
public class Team {
public HashSet<string> PlayerIds { get; set; }
public List<int> CoachIds { get; set; }
}
And you'd be able to successfully update them.
You could override the TrySetPropertyValue
method of the Delta class and make use of JArray class:
public sealed class DeltaWithCollectionsSupport<T> : Delta<T> where T : class
{
public override bool TrySetPropertyValue(string name, object value)
{
var propertyInfo = typeof(T).GetProperty(name);
return propertyInfo != null && value is JArray array
? base.TrySetPropertyValue(name, array.ToObject(propertyInfo.PropertyType))
: base.TrySetPropertyValue(name, value);
}
}
If you are using the ODataMediaTypeFormatter, this should be working. There are a couple of caveats to mention though.
1) your collections have to be settable.
2) the entire collection is replaced. you cannot remove/add individual elements.
Also, there is an issue tracking item 1 - '670 -Delta should support non-settable collections.'