c# - WebApi Put如何从指定的属性中告诉未指定的属性设置为null?

标签 c# .net json asp.net-web-api

这里是场景。有一个 web api put 调用来更改 sql server 数据库中的对象。如果它们在 webapi 调用 json 中明确指定,我们只想更改数据库对象上的字段。例如:

{ "Name":"newName", "Colour":null }

这应该将“名称”字段更改为“newName”,将“颜色”字段更改为空。与这个json相反:

{ "Name":"newName" }

应该只更改名称字段,保持旧的颜色值不变。

使用 WebApi 检测属性是否通过的好方法是什么?

如果我这样定义我的方法:

[HttpPut]
[Route("/item/{id}")]
public void ChangeItem(int id, Item item)
{
    ...
}

item.Colour 在任何一种情况下都将为空。请注意,我在这里使用各种数据类型,示例中的属性 Colour 可能是 intstring日期时间Guid

我知道我可以使用 [FromBody] 属性获取原始 json,然后自己解析它,但似乎默认 Binder 已经完成了大部分工作(包括验证),所以我会很好奇如何重用它,但也能实现我想要的。最简单的方法是什么?

更新:

我想澄清一下,我是一个“偶尔连接”的场景。也就是说,使用 API 的设备大部分时间都在网络覆盖范围之外,它们会不时使用 API 进行同步。

实际上,这意味着需要同步的大部分数据都聚合为零个或一个“推送更新到服务器”调用,然后是“从服务器获取最新状态”调用。在后端使用 Sql Server 和 EF 会导致几个不同(有时是不相关的)实体包含在单个 json 中。例如:

class TaskData
{ 
    public IList<User> AssignedUsers {get; set;} 
    public IList<Product> Products {get; set;} 
    public Task Task {get; set}
}

此外,用于为 GET 调用生成 json 的模型类与 EF 实体是分开的,因为数据库架构与 API 对象模型不完全匹配。

最佳答案

我最终为属性使用了动态代理,这样我就可以将 JsonMediaTypeFormatter 编写的属性标记为“脏”。我用了稍微修改的yappi (实际上不必修改它,只是想 - 如果下面的代码与 yappi 示例/API 不完全匹配,请提及这一点)。我猜你可以使用你最喜欢的动态代理库。只是为了好玩,我尝试将其移植到 NProxy.Core但这不起作用,因为出于某种原因 json.net 拒绝写入 NProxy.Core 生成的代理。

所以它是这样工作的。我们有一个这样的基类:

public class DirtyPropertiesBase
{
    ...

    // most of these come from Yappi
    public static class Create<TConcept> where TConcept : DirtyPropertiesBase
    {
        public static readonly Type Type =PropertyProxy.ConstructType<TConcept, PropertyMap<TConcept>>(new Type[0], true);
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }

    private readonly List<string> _dirtyList = new List<string>();

    protected void OnPropertyChanged(string name)
    {
        if (!_dirtyList.Contains(name))
        {
            _dirtyList.Add(name);
        }
    }
    public bool IsPropertyDirty(string name)
    {
        return _dirtyList.Contains(name);
    }

    ...
    // some more Yappi specific code that calls OnPropertyChanged
    // when a property setter is called
}

在代理实现的某个地方,我们调用 OnPropertyChanged 以便我们记住写入了哪些属性。

然后我们有我们的自定义JsonCreationConverter:

class MyJsonCreationConverter : JsonConverter
{
    private static readonly ConcurrentDictionary<Type, Func<DirtyPropertiesBase>> ContructorCache = new ConcurrentDictionary<Type, Func<DirtyPropertiesBase>>();
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException("MyJsonCreationConverter should only be used while deserializing.");
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }

        Func<DirtyPropertiesBase> constructor = ContructorCache.GetOrAdd(objectType, x =>
            (Func<DirtyPropertiesBase>)typeof(DirtyPropertiesBase.Create<>).MakeGenericType(objectType).GetField("New").GetValue(null));

        DirtyPropertiesBase value = constructor();
        serializer.Populate(reader, value);
        return value;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (DirtyPropertiesBase).IsAssignableFrom(objectType);
    }
}

这里的想法是,当 JsonMediaTypeFormatter 转换传入的 json 时,我们将初始的空对象替换为我们之前定义的代理。

我们像这样在 WebApiConfig.cs 中注册这个转换器

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MyJsonCreationConverter());

现在,当我们的模型由 json 填充代替从 DirtyPropertiesBase 派生的每个对象时,将有一个具有正确填充的 _dirtyList 集合的代理。现在我们只需要将这些模型中的每一个映射回 EF 实体。这很简单,AutoMapper .我们像这样注册每个模型:

Mapper.CreateMap<Model, Entity>().ForAllMembers(x => x.Condition(z => ((Model)z.Parent.SourceValue).IsPropertyDirty(z.MemberName)));

然后你就有了常用的映射代码:

Entity current = _db.Entity.Single(x => x.Id == Id);
Mapper.Map(update, current);
_db.SaveChanges();

这将确保只更新脏属性。

关于c# - WebApi Put如何从指定的属性中告诉未指定的属性设置为null?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30877368/

相关文章:

c# - 带有 C# 代码的 html 表单

c# - 我可以从 Razor 局部 View 中填充母版页中的 ContentPlaceHolder 吗?

c# - 信息标签

c# - 根据屏幕分辨率将 Windows 窗体上的按钮控件调整为固定大小

javascript - 如何从 JSON 到 JS 获取键的值?

sql - PostgreSQL json路径表达式以查找具有特定键的第一个数组元素

c# - XmlReader 和 MemoryStream,返回的 xml 缺少标签

c++ - 如何声明 handel 数组?

.net - 我怎样才能使它与深层属性一起工作

php - 将 android 设备连接到 SqlServer 2005 时出现 JSON 解析错误