首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >WebApi如何区分未指定的属性和设置为null的指定属性?

WebApi如何区分未指定的属性和设置为null的指定属性?
EN

Stack Overflow用户
提问于 2015-06-17 04:18:37
回答 4查看 6.3K关注 0票数 24

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

代码语言:javascript
复制
{ "Name":"newName", "Colour":null }

这应该将Name字段更改为"newName“,并将"Colour”字段更改为null。与这个json相反:

代码语言:javascript
复制
{ "Name":"newName" }

这应该只会更改Name字段,而保持旧的Color值不变。

使用WebApi检测是否传递了属性的好方法是什么?

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

代码语言:javascript
复制
[HttpPut]
[Route("/item/{id}")]
public void ChangeItem(int id, Item item)
{
    ...
}

无论哪种情况,item.Colour都将为null。请注意,我在这里使用的是各种数据类型,示例中的属性Colour可以是intstringDateTimeGuid等。

我知道我可以获得带有[FromBody]属性的原始json,然后自己解析它,但是看起来默认绑定器已经完成了大部分工作(包括验证),所以我很好奇如何重用它,同时又能实现我想要的。最简单的方法是什么?

更新

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

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

代码语言:javascript
复制
class TaskData
{ 
    public IList<User> AssignedUsers {get; set;} 
    public IList<Product> Products {get; set;} 
    public Task Task {get; set}
}

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-06-19 15:01:15

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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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中注册这个转换器,如下所示

代码语言:javascript
复制
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MyJsonCreationConverter());

现在,当我们的模型由_dirtyList填充时,将会有一个具有正确填充的json集合的代理。现在我们只需要将这些模型中的每一个映射回EF实体。这对于AutoMapper来说已经很简单了。我们按如下方式注册每个模型:

代码语言:javascript
复制
Mapper.CreateMap<Model, Entity>().ForAllMembers(x => x.Condition(z => ((Model)z.Parent.SourceValue).IsPropertyDirty(z.MemberName)));

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

代码语言:javascript
复制
Entity current = _db.Entity.Single(x => x.Id == Id);
Mapper.Map(update, current);
_db.SaveChanges();

这将确保只更新Dirty属性。

票数 4
EN

Stack Overflow用户

发布于 2015-06-19 15:28:17

虽然针对OData服务进行了介绍,但您可以尝试使用System.Web.Http.OData.Delta。这允许对实体进行部分更新。

有关使用Delta<T>的很好的讨论,请看this blog post。从本质上讲,它归结为定义PutPatch方法,例如:

代码语言:javascript
复制
public class MyController : ApiController
{
    // Other actions omitted…

    [AcceptVerbs("Patch")]
    public async Task<IHttpActionResult> Patch(int key, Delta<Item> model)
    {
        var entity = _items.FindAsync(o => o.Id == key);

        if (entity == null) {
            return NotFound();
        }

        model.Patch(entity);

        return StatusCode(HttpStatusCode.NoContent);
    }

    public async Task<IHttpActionResult> Put(int key, Delta<Item> model)
    {
        var entity = _items.FindAsync(o => o.Id == key);

        if (entity == null) {
            return NotFound();
        }

        model.Put(entity);

        return StatusCode(HttpStatusCode.NoContent);
    }
}

在这里,对Put的请求将更新整个模型,而对Patch的请求将只更新部分模型(仅使用客户端传递的属性)。

票数 1
EN

Stack Overflow用户

发布于 2015-06-17 19:25:23

当然,这是一个持久性问题,而不是模型绑定器问题。

对于给定的属性,您的API被提供了一个空值,因此绑定器正在使用它。

也许在持久性中,您可以建议使用哪个框架来忽略空条目(我假设您传递的是可空的int,而不仅仅是int)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30877368

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档