我目前有一个实现RESTFul API的Web API。我的API的模型如下所示:
public class Member
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Created { get; set; }
public DateTime BirthDate { get; set; }
public bool IsDeleted { get; set; }
}
我已经实施了PUT
方法来更新与此类似的行(为了简洁起见,我省略了一些不相关的内容):
[Route("{id}")]
[HttpPut]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
// Do some error checking
// ...
// ...
var myDatabaseEntity = new BusinessLayer.Member(id);
myDatabaseEntity.FirstName = model.FirstName;
myDatabaseEntity.LastName = model.LastName;
myDatabaseEntity.Created = model.Created;
myDatabaseEntity.BirthDate = model.BirthDate;
myDatabaseEntity.IsDeleted = model.IsDeleted;
await myDatabaseEntity.SaveAsync();
}
使用PostMan,我可以发送以下JSON,一切正常:
{
firstName: "Sara",
lastName: "Smith",
created: '2018/05/10",
birthDate: '1977/09/12",
isDeleted: false
}
如果我将此身份作为我的身体发送到http://localhost:8311/api/v1/Member/12
作为PUT请求,ID为12的数据中的记录会更新为您在JSON中看到的记录。
我想做的是实现PATCH动词,我可以在其中进行部分更新。如果萨拉结婚,我希望能够发送这个JSON:
{
lastName: "Jones"
}
我希望能够只发送JSON并更新LastName
字段并保留所有其他字段。
我试过了:
[Route("{id}")]
[HttpPatch]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
}
我的问题是这会返回model
对象中的所有字段(除LastName
字段外都是空字符),这是有道理的,因为我说我想要{{1}对象。我想知道的是,是否有办法检测JSON请求中实际发送了哪些属性,以便我只更新这些字段?
答案 0 :(得分:9)
PATCH
操作通常不会使用与POST
或PUT
操作完全相同的模型来定义:您如何区分null
和don't change
。 From the IETF:
但是,使用PATCH,随附的实体包含一组 说明资源当前如何驻留在 应修改原始服务器以生成新版本。
您可以查看here的PATCH
建议,但是sumarilly是:
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
答案 1 :(得分:1)
@Tipx的使用PATCH
的答案是正确的,但是您可能已经发现,用像C#这样的静态类型的语言来实现它实际上不是一件容易的事。
如果您使用PATCH
来表示单个域实体的一组部分更新(例如,仅为具有更多属性的联系人更新名和姓),则需要按照循环“ PATCH”请求中的每条指令,然后将该指令应用于类的实例的方式进行操作。
应用单个指令将包含
对于完整.NET Framework上的Web API 2,JSONPatch github project似乎在提供此代码方面起了阻碍作用,尽管最近看起来该回购协议上没有很多开发,并且自述文件确实状态:
这仍然是一个早期项目,请勿在生产中使用 除非您了解来源并且不介意修复一些错误 ;)
.NET Core上的事情比较简单,因为它在Microsoft.AspNetCore.JsonPatch
namespace中具有支持此功能的一组功能。
相当有用的jsonpatch.com网站还列出了.NET中Patch的更多选项:
- Asp.Net Core JsonPatch(Microsoft官方实施)
- Ramone(使用REST服务的框架,包括JSON Patch实现)
- JsonPatch(将JSON补丁支持添加到ASP.NET Web API)
- Starcounter(内存应用程序引擎,使用OT的JSON修补程序进行客户端-服务器同步)
- Nancy.JsonPatch(向NancyFX添加了JSON补丁支持)
- Manatee.Json(所有JSON,包括JSON补丁)
我需要将此功能添加到我们现有的Web API 2项目中,因此,如果在执行此操作时发现其他有用的内容,我将更新此答案。
答案 2 :(得分:1)
我希望这有助于使用Microsoft JsonPatchDocument:
.Net Core 2.1将操作修补到控制器中
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody]JsonPatchDocument<Node> value)
{
try
{
//nodes collection is an in memory list of nodes for this example
var result = nodes.FirstOrDefault(n => n.Id == id);
if (result == null)
{
return BadRequest();
}
value.ApplyTo(result, ModelState);//result gets the values from the patch request
return NoContent();
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex);
}
}
节点模型类:
[DataContract(Name ="Node")]
public class Node
{
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "node_id")]
public int Node_id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "full_name")]
public string Full_name { get; set; }
}
有效的补丁JSon仅更新“ full_name”和“ node_id”属性将是一系列操作,例如:
[
{ "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
{ "op": "replace", "path": "node_id", "value": 10}
]
如您所见,“ op”是您要执行的操作,最常见的一个是“ replace”,它将为新属性设置该属性的现有值,但还有其他操作:
[
{ "op": "test", "path": "property_name", "value": "value" },
{ "op": "remove", "path": "property_name" },
{ "op": "add", "path": "property_name", "value": [ "value1", "value2" ] },
{ "op": "replace", "path": "property_name", "value": 12 },
{ "op": "move", "from": "property_name", "path": "other_property_name" },
{ "op": "copy", "from": "property_name", "path": "other_property_name" }
]
这是我基于C#中的Patch(“替换”)规范构建的扩展方法,使用反射可用于序列化任何对象以执行Patch(“替换”)操作,还可以传递所需的Encoding它将返回准备发送到httpClient.PatchAsync(endPoint,httpContent)的HttpContent(StringContent):
public static StringContent ToPatchJsonContent(this object node, Encoding enc = null)
{
List<PatchObject> patchObjectsCollection = new List<PatchObject>();
foreach (var prop in node.GetType().GetProperties())
{
var patch = new PatchObject{ Op = "replace", Path = prop.Name , Value = prop.GetValue(node) };
patchObjectsCollection.Add(patch);
}
MemoryStream payloadStream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(patchObjectsCollection.GetType());
serializer.WriteObject(payloadStream, patchObjectsCollection);
Encoding encoding = enc ?? Encoding.UTF8;
var content = new StringContent(Encoding.UTF8.GetString(payloadStream.ToArray()), encoding, "application/json");
return content;
}
}
注意到tt也使用我创建的此类使用DataContractJsonSerializer序列化PatchObject:
[DataContract(Name = "PatchObject")]
class PatchObject
{
[DataMember(Name = "op")]
public string Op { get; set; }
[DataMember(Name = "path")]
public string Path { get; set; }
[DataMember(Name = "value")]
public object Value { get; set; }
}
有关如何使用扩展方法以及如何使用HttpClient调用Patch请求的C#示例:
var nodeToPatch = new { Name = "TestPatch", Private = true };//You can use anonymous type
HttpContent content = nodeToPatch.ToPatchJsonContent();//Invoke the extension method to serialize the object
HttpClient httpClient = new HttpClient();
string endPoint = "https://localhost:44320/api/nodes/1";
var response = httpClient.PatchAsync(endPoint, content).Result;
您可以在GitHub上查看我的两个示例:
https://github.com/ernestleyva/SampleRestfulWebApi
https://github.com/ernestleyva/HttpClientApiService
谢谢
答案 3 :(得分:0)
我想实现完全相同的目标,但是使用了与此处所述其他方法不同的方法。
首先,我创建了一个UpdateModel
,其中包含可以更新的所有字段,并使所有字段都可以为空。
public class UpdateModel
{
public double? Temperature { get; set; }
public int? Stock { get; set; }
public string Name { get; set; }
}
此更新模型与您要更新的对象的id
一起发送到服务层。
然后,服务层将此映射到database model
并发送到存储库层。在存储库层更新方法中,我们要找到一个现有模型,然后将更新模型中的所有非空字段应用于该模型。我用反射来做到这一点:
public async Task<DbModel> Update(DbModel updateModel)
{
// Find the existing model from the database
var objToUpdate = await _context.Objects.FindAsync(updateModel.Id);
if (mineToUpdate == null)
{
return null;
}
// Loop through all properties of the update model
foreach (PropertyInfo prop in mine.GetType().GetProperties())
{
// If they are not null, update the property on the database model
if (prop.GetValue(mine) != null)
{
mineToUpdate
.GetType()
.GetProperty(prop.Name)
.SetValue(mineToUpdate, prop.GetValue(mine));
}
}
await _context.SaveChangesAsync();
return mineToUpdate;
}
使用此方法,只要您从更新DTO到数据库模型的映射正确,就可以更改模型而不必担心更新逻辑。