以下是该方案。有一个web api put调用来更改sql server数据库中的对象。我们只想更改数据库对象上的字段,如果它们是在webapi调用json上明确指定的。例如:
{ "Name":"newName", "Colour":null }
这应该将名称字段更改为" newName"和"颜色"字段为null。与此json相反:
{ "Name":"newName" }
应仅更改“名称”字段,保持旧的“颜色”值不变。
WebApi检测属性是否通过的好方法是什么?
如果我这样定义我的方法:
[HttpPut]
[Route("/item/{id}")]
public void ChangeItem(int id, Item item)
{
...
}
在任何一种情况下, item.Colour
都将为null。请注意,我在此处使用了各种数据类型,示例中的属性Colour
可以是int
,string
,DateTime
,{{1}等等。
我知道我可以使用Guid
属性获取原始json,然后自己解析它,但似乎默认绑定器已经完成了大部分工作(包括验证),所以我很好奇我可以重复使用它,但也实现我想要的。什么是最简单的方法?
更新:
我想澄清一下,我的是偶尔连接的"场景。也就是说,使用API的设备大多数时间都不在网络覆盖范围内,并且它们会不时使用API进行同步。
实际上,这意味着同步所需的大多数数据都会聚合到零或一个“推送服务器更新”中。呼叫后跟"从服务器获取最新状态"呼叫。使用后端的Sql Server和EF导致几个不同的(有时是无关的)实体包含在单个json中。例如:
[FromBody]
用于为GET调用生成json的模型类与EF Entites是分开的,因为数据库模式与API对象模型不完全匹配。
答案 0 :(得分:4)
我最终使用动态代理来处理属性,因此我可以将JsonMediaTypeFormatter
写的属性标记为“脏”。我使用了稍微修改过的yappi(没有真正修改它,只是想 - 如果下面的代码与yappi samples / 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();
这将确保只更新Dirty属性。
答案 1 :(得分:1)
虽然为OData服务引入,但您可以尝试使用System.Web.Http.OData.Delta<T>
。这允许实体的部分更新。
请查看this blog post,了解有关使用Delta<T>
的详细讨论。从本质上讲,它归结为定义Put
和Patch
方法,例如:
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
的请求将仅部分更新模型(仅使用客户端传递的属性)。
答案 2 :(得分:0)
当然这是一个持久性问题,而不是模型绑定器问题。
您的API为给定属性提供了空值,因此绑定器正在遵守它。
也许在持久性中,您可以建议您使用哪个框架来忽略空条目(我假设您已经通过了可空的int而不仅仅是整数)
答案 3 :(得分:0)
我通过使用这种模式解决了这个问题。
public class ValuesController : ApiController
{
public void Put(int id, [FromBody]Item value)
{
if (value.NameSpecified)
{
}
else
{
}
}
}
public class Item
{
internal bool NameSpecified = false;
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NameSpecified = true;
}
}
}