How do I patch enumerables with System.Web.Http.OD

2019-03-22 03:01发布

问题:

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.

回答1:

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.



回答2:

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);
    }
}


回答3:

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.'