5

我正在使用 HttpPatch 部分更新对象。为了使其正常工作,我使用了 OData 的 Delta 和 Patch 方法(此处提到:目前推荐的使用 Web API 执行部分更新的方法是什么?)。一切似乎都运行良好,但注意到映射器区分大小写;当传递以下对象时,属性将获得更新的值:

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

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

是否有一个配置部分会使用补丁忽略区分大小写?

供参考:

在输出中,我使用以下格式化程序具有驼峰式属性(遵循 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
}
4

2 回答 2

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)

您仍然可以像以前一样使用 patch 方法:

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

在此调用中,不能通过 patch 方法更改 Deleted 和 ID 属性。

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

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

检查道具的示例

于 2013-10-29T15:26:18.323 回答
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;
        }
}
于 2014-10-15T19:49:27.657 回答