【问题标题】:asp.net mvc web api partial update with OData Patch带有 OData 补丁的 asp.net mvc web api 部分更新
【发布时间】:2013-10-22 09:12:59
【问题描述】:

我正在使用 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"
}

但是当我传递具有小写或驼峰属性的同一对象时,补丁不起作用 - 新值没有通过,所以看起来反序列化和属性映射存在问题,即:“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
}

【问题讨论】:

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


    【解决方案1】:

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

    长答案:我今天遇到了和你一样的问题,这就是我解决它的方法。
    我发现它必须区分大小写非常烦人,因此我决定取消整个 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 属性无法通过 patch 方法更改。

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

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

    【讨论】:

    • 嗨 Rik,谢谢你的回答,我已经添加了代码,但有一些差异(现在使用 MVC 5)并且它不起作用:` var props = from p in this.GetType( ).GetProperties() let attr = p.GetCustomAttributes(typeof(NotPatchableAttribute), false) where attr == null select p; ` 但是,该查询不返回任何内容。
    • @303 哦..我正在使用 web api 2,让我在 mvc 5 上快速测试它,我会回复你.. 没有什么是无法解决的 :)
    • @303 我刚刚在一个新的mvc项目中测试了它,它似乎可以工作。为了速度,我只是调整了编辑方法来使用补丁,它可以工作。我把代码放到github上供大家对比。
    • 仅供参考:我使用的是 .Net 4.5,但出现此错误:“System.Reflection.PropertyInfo”不包含“GetCustomAttribute”的定义,并且没有扩展方法“GetCustomAttribute”接受第一个参数可以找到类型“System.Reflection.PropertyInfo”(您是否缺少 using 指令或程序集引用?)我需要额外的引用吗?
    • @303 你需要使用 System.Reflection;当然。我刚刚再次克隆了我的示例应用程序,为其创建了一个新的解决方案并重新安装(卸载并再次安装)以下打包的块:实体框架、asp.net mvc 和 asp.net web 优化框架。在此之后,我的所有参考都正常,我能够构建。运行应用程序时,它似乎对我有用...
    【解决方案2】:

    使用继承 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;
            }
    }
    

    【讨论】:

    • 这对我有用,但我错过了 DynamicObjectValueProvider,我找到了实现 here 以及我无法工作的 DeltaContractResolver 实现
    • 如果其他人想知道,您可以像这样在 WebApiConfig 中设置合约解析器 config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DeltaContractResolver();
    猜你喜欢
    • 2014-06-11
    • 1970-01-01
    • 2014-10-25
    • 1970-01-01
    • 2023-01-30
    • 2021-07-27
    • 2012-03-11
    • 2014-07-10
    • 1970-01-01
    相关资源
    最近更新 更多