Technetium
Technetium

Reputation: 6158

Implementing JSON Merge Patch in ASP.NET Core WebAPI

I am interested in adding support for partial updates in my ASP.NET Core WebAPI where I only update the properties on a resource that the caller provided, leaving excluded properties unchanged.

For context, imagine I have a resource that can be described as follows:

GET /users/1
{
    title: "Mister",
    firstName: "Frederick",
    middleName: "McFeely",
    lastName: "Rodgers"
}

If I wanted to allow consumers to change the value stored in the firstName property from "Frederick" to "Fred" in isolation, I should be able to expose a PATCH endpoint that supports the JSON Merge Patch Content-Type, like so:

PATCH /users/1
Content-Type: application/merge-patch+json
{
    firstName: "Fred"
}

However, I see no easy way for me to know that firstName is the only property being updated. For example, if I were to make a controller that accepted PATCH verbs, it could be scaffolded like this:

[Route("users")]
public class UsersController : Controller {

    [HttpPatch("{userId:int}")]
    public User Patch([FromRoute] int userId, [FromBody] User user) {

        // How do I know which properties were set on User at this point?

    }

}

public class User {

    public String Title { get; set; }
    public String FirstName { get; set; }
    public String MiddleName { get; set; }
    public String LastName { get; set; }

}

But I don't see how I can extract which properties' had keys defined on the JSON object before it was hydrated as a User and passed to my controller. I cannot assume a value of null to mean a property was excluded as the caller could be explicitly setting an optional property to null.

Edit

I am aware of the Microsoft.AspNetCore.JsonPatch library. This, unfortunately, expects the caller to use the "[description of changes]" to define a PATCH as described in RFC 5789, which I find unintuitive and verbose. I am referring to the "JSON Merge Patch" defined in RFC 7396.

Upvotes: 19

Views: 7797

Answers (5)

Claas Diederichs
Claas Diederichs

Reputation: 156

for simple types, I found a very simple solution using Newtonsoft.Json merge of JObjects:

public static T Patched<T>(T source, JObject patch) where T : class
{
    var sourceObject = JObject.FromObject(source);
    sourceObject.Merge(patch, new JsonMergeSettings() {MergeArrayHandling = MergeArrayHandling.Union});
    return sourceObject.ToObject<T>();
}

public static T Patched<T>(T source, string patchCode) where T : class
{
    return Patched<T>(source, JObject.Parse(patchCode));
}

Hope this helps someone searching for this topic and looking for a simple solution without external packages.

Upvotes: 4

whyleee
whyleee

Reputation: 4049

I found a library that works: https://github.com/Morcatko/Morcatko.AspNetCore.JsonMergePatch

[HttpPatch]
[Consumes(JsonMergePatchDocument.ContentType)]
public void Patch([FromBody] JsonMergePatchDocument<Model> patch)
{
    ...
    patch.ApplyTo(backendModel);
    ...
}

Or use patch.JsonPatchDocument.Operations to walk through patch request fields manually.

Upvotes: 6

Gopal Krishnan
Gopal Krishnan

Reputation: 1116

It appears like, for merge patch you will have to wait for odata support.

It is in beta at the moment and supports the merge semantics with the Delta<> class.

https://www.nuget.org/packages/Microsoft.AspNetCore.OData/

Upvotes: 1

Vyrotek
Vyrotek

Reputation: 5459

What you might be looking for is ASP.Net Core JsonPatchDocument

https://github.com/aspnet/JsonPatch

https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.jsonpatch

Upvotes: -3

Antoshjke
Antoshjke

Reputation: 462

For doing a patch, you have to define PatchDocument.

More about it you can find PatchDocument

Example of method.

 [HttpPatch("{userId:int}")]   
 public IActionResult UserPatch(int userId, [FromBody] JsonPatchDocument<User> patchDocument) {

    var user = new User();
    // Because it comes from url.
    user.Id = userId;
    patchDocument.ApplyTo(user);

    // Here you call context or repository to save.

  }

Example of document.

[
  { "op": "replace", "path": "/firstName", "value": "boo" },
]

That will update firstName field to 'boo' in user model.

Upvotes: -3

Related Questions