Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I patch enumerables with System.Web.Http.OData.Delta?

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.

like image 988
aknuds1 Avatar asked Oct 06 '22 09:10

aknuds1


1 Answers

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.

like image 84
Filip W Avatar answered Oct 10 '22 01:10

Filip W