如何实现补丁HTTP?

时间:2017-12-14 11:38:14

标签: c# asp.net-web-api2 patch

我正在使用Microsoft Web API 2开发Web服务。

我有一个人控制器,我会处理补丁HTTP请求。在我的人员控制器下面:

namespace MyAssembly.WebApi.Controllers
{
    [RoutePrefix("api/Person")]
    public class PersonController : BaseController
    {
        [HttpGet]
        public IHttpActionResult GetAll(int id)
        {
            // get logic...
        }

        [HttpPost]
        [Route("api/Person/{id:int}")]
        public IHttpActionResult Create(int id, Person dto)
        {
            // post logic...
        }

    [HttpPatch]
    [Route("api/Person/{id:int}")]
    public IHttpActionResult Update(int id, object dto)
    {
        Person currentPerson = myRepo.Get(id);

        currentPerson.patch(dto); // <-- How can I "patch" currentPerson object?

        myRepo.Update(id, currentPerson);

        return Ok();
    }
}

在我的人物对象下面:

public class Person
{
    public string Name { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }

    public int[] PreferredNumbers { get; set; }

    public string[] PreferredSerieTV { get; set; }

    public Address Address { get; set; }
}

我的目标是使用currentPerson对象中的所有指定属性更新dto对象的所有属性。

我的HTTP补丁请求应如下所示:

PATCH /api/Person/1
Host: localhost
Content-Type: application/json
Cache-Control: no-cache

{
    "Name": "Alessia",
    "PreferredNumbers": [1,2,3],
    "PreferredSerieTV": ["Serie1", "Serie2"]
}

我尝试过使用Delta对象,但整数矩阵存在已知问题。以前我的签名如下:

public IHttpActionResult Update(int id, Delta<Person> dto) { ... }

很高兴:支持http请求的小写属性。

如何处理补丁请求?

[编辑]

我已经集成了您的解决方案,但我收到了此错误:

original
System.ArgumentException: 'Un oggetto di tipo 'Newtonsoft.Json.Linq.JArray' non può essere convertito nel tipo 'System.Int32[]'.'

traduction
System.ArgumentException: 'An object type 'Newtonsoft.Json.Linq.JArray' cannot be converted to type 'System.Int32[]'.'

引发异常的代码行是这样的:

private static void updateObjectPropertyCore<TObject>(TObject target, string propertyName, object value) where TObject : class
{
    var type = typeof(TObject);
    var property = type.GetPropertyCaseInsensitive(propertyName);
    if (property != null && property.CanWrite)
    {
        property.SetValue(target, value); <---
    }
}

非常感谢

1 个答案:

答案 0 :(得分:1)

我为PUT和PATCH请求做了类似的事情。

仅接受属性以更改重构操作以接受IDictionary<string, object>以保存已更改的属性以进行更新/修补。

[HttpPatch]
[Route("api/Person/{id:int}")]
public IHttpActionResult Update(int id, [FromBody] Dictionary<string, object> dto) { ... }

然后,可以使用以下扩展方法

来访问匹配属性
public static void patchObject<TObject>(this TObject target, Dictionary<string, object> values) where TObject : class {
    if (target != null) {
        foreach (var kvp in values) {
            updateObjectPropertyCore<TObject>(target, kvp.Key, kvp.Value);
        }
    }
}

private static void updateObjectPropertyCore<TObject>(TObject target, string propertyName, object value) where TObject : class {
    var type = typeof(TObject);
    var property = type.GetPropertyCaseInsensitive(propertyName);
    if (property != null && property.CanWrite) {
        object coercedValue;
        var destinationType = property.PropertyType;
        try {
            coercedValue = Convert.ChangeType(value, destinationType, CultureInfo.CurrentCulture);
        } catch {
            return destinationType.IsValueType ? null : Activator.CreateInstance(destinationType);
        }
        property.SetValue(target, coercedValue);
    }
}

/// <summary>
/// Gets a property by name, ignoring case and searching all interfaces.
/// </summary>
/// <param name="type">The type to inspect.</param>
/// <param name="propertyName">The property to search for.</param>
/// <returns>The property or null if not found.</returns>
public static PropertyInfo GetPropertyCaseInsensitive(this Type type, string propertyName) {

    var typeList = new List<Type> { type };

    if (type.IsInterface()) {
        typeList.AddRange(type.GetInterfaces());
    }

    var flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance;

    return typeList
        .Select(interfaceType => interfaceType.GetProperty(propertyName, flags))
        .FirstOrDefault(property => property != null);
}

所以现在你可以在要修补的对象上调用扩展方法。

[HttpPatch]
[Route("api/Person/{id:int}")]
public IHttpActionResult Update(int id, [FromBody] Dictionary<string, object> dto) {
    var currentPerson = myRepo.Get(id);

    if(currentPerson == null)
        return NotFound();

    currentPerson.patchObject(dto); // <-- matching keys in dto will modify target object

    myRepo.Update(id, currentPerson);

    return Ok();
}