I currently have a Web API that implements a RESTFul API. The model for my API looks like this:
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; } }
I've implemented a PUT
method for updating a row similar to this (for brevity, I've omitted some non-relevant stuff):
[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(); }
Using PostMan, I can send the following JSON and everything works fine:
{ firstName: "Sara", lastName: "Smith", created: "2018/05/10", birthDate: "1977/09/12", isDeleted: false }
If I send this as my body to http://localhost:8311/api/v1/Member/12
as a PUT request, the record in my data with ID of 12 gets updated to what you see in the JSON.
What I would like to do though is implement a PATCH verb where I can do partial updates. If Sara gets married, I would like to be able to send this JSON:
{ lastName: "Jones" }
I would like to be able to send just that JSON and update JUST the LastName
field and leave all the other fields alone.
I tried this:
[Route("{id}")] [HttpPatch] public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id, [FromBody]Models.Member model) { }
My problem is that this returns all the fields in the model
object (all of them are nulls except the LastName
field), which makes sense since I am saying I want a Models.Member
object. What I would like to know is if there is a way to detect which properties have actually been sent in the JSON request so I can update just those fields?
HTTP POST can support partial updates to a resource. But there is a separate PATCH method. This new HTTP method, PATCH, modifies an existing HTTP resource. The call to the Patch method is sufficient to partially update the corresponding properties of the Employee object in the list.
When a client needs to replace an existing Resource entirely, they can use PUT. When they're doing a partial update, they can use HTTP PATCH. For instance, when updating a single field of the Resource, sending the complete Resource representation can be cumbersome and uses a lot of unnecessary bandwidth.
JSON Patch is a format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed.
I hope this helps using Microsoft JsonPatchDocument:
.Net Core 2.1 Patch Action into a Controller:
[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); } }
Node Model class:
[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; } }
A valid Patch JSon to update just the "full_name" and the "node_id" properties will be an array of operations like:
[ { "op": "replace", "path": "full_name", "value": "NewNameWithPatch"}, { "op": "replace", "path": "node_id", "value": 10} ]
As you can see "op" is the operation you would like to perform, the most common one is "replace" which will just set the existing value of that property for the new one, but there are others:
[ { "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" } ]
Here is an extensions method I built based on the Patch ("replace") specification in C# using reflection that you can use to serialize any object to perform a Patch ("replace") operation, you can also pass the desired Encoding and it will return the HttpContent (StringContent) ready to be sent to httpClient.PatchAsync(endPoint, httpContent):
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; }
}
Noticed that tt also uses this class I created to serialize the PatchObject using DataContractJsonSerializer:
[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; } }
A C# example of how to use the extension method and invoking the Patch request using HttpClient:
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;
Thanks
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With