使用OData Patch进行asp.net mvc web api部分更新

时间:2013-10-22 09:12:59

标签: c# api asp.net-mvc-4 odata

我正在使用HttpPatch来部分更新对象。为了实现这一点,我正在使用OData的Delta和Patch方法(这里提到:What's the currently recommended way of performing partial updates with Web API?)。一切似乎工作正常,但注意到映射器区分大小写;当传递以下对象时,属性将获得更新的值:

{
  "Title" : "New title goes here",
  "ShortDescription" : "New text goes here"
}

但是当我传递具有较低或驼峰式属性的相同对象时,Patch不起作用 - 新值不会通过,因此看起来反序列化和属性映射存在问题,即:“shortDescription”到“ShortDescription”。

是否有使用Patch忽略区分大小写的配置部分?

供参考:

在输出中,我使用以下格式化程序具有驼峰式属性(遵循REST最佳实践):

//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;

//sample output
{
  "title" : "First",
  "shortDescription" : "First post!"
}

然而,我的模型类遵循C#/ .NET格式约定:

public class Entry {
  public string Title { get; set;}
  public string ShortDescription { get; set;}
  //rest of the code omitted
}

2 个答案:

答案 0 :(得分:7)

简短回答,没有没有配置选项可以撤消区分大小写(据我所知)

答案很长:我今天和你有同样的问题,这就是我解决这个问题的方法。
我发现它必须区分大小写令人难以置信,因此我决定取消整个oData部分,因为它是一个巨大的图书馆,我们正在滥用....

可以在我的github github

找到此实现的示例

我决定实施自己的补丁方法,因为那是我们实际缺乏的力量。我创建了以下抽象类:

public abstract class MyModel
{
    public void Patch(Object u)
    {
        var props = from p in this.GetType().GetProperties()
                    let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
                    where attr == null
                    select p;
        foreach (var prop in props)
        {
            var val = prop.GetValue(this, null);
            if (val != null)
                prop.SetValue(u, val);
        }
    }
}

然后我让我的所有模型类继承自* MyModel *。注意我使用* let *的行,我将在以后解释。因此,现在您可以从控制器操作中删除Delta,并再次使其成为Entry,就像使用put方法一样。例如

public IHttpActionResult PatchUser(int id, Entry newEntry)

您仍然可以按照以前的方式使用补丁方法:

var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();

现在,让我们回到

let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))

我发现存在安全风险,任何属性都可以通过补丁请求进行更新。例如,您现在可能希望修补程序可以更改ID。我创建了一个自定义属性来装饰我的属性。 NotPatchable属性:

public class NotPatchableAttribute : Attribute {}

您可以像使用其他任何属性一样使用它:

public class User : MyModel
{
    [NotPatchable]
    public int ID { get; set; }
    [NotPatchable]
    public bool Deleted { get; set; }
    public string FirstName { get; set; }
}

在此调用中,通过补丁方法无法更改Deleted和ID属性。

我希望这也能为你解决。如果您有任何疑问,请随时发表评论。

我添加了一个截图,检查了一个新的mvc 5项目中的道具。如您所见,结果视图中填充了Title和ShortDescription。

Example of inspecting the props

答案 1 :(得分:3)

使用自定义合约解析程序可以很容易地完成它,该解析程序继承CamelCasePropertyNamesContractResolver并实现CreateContract方法,该方法查看delta的具体类型并获取实际的属性名称,而不是使用来自json的属性名称。摘要如下:

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