4

I'm trying to use JsonPatch in ASP.NET Core to handle partial updates for a model but am having binding issues when the PATCH is sent through to the Web API controller action:

I'm using a small library to make the PATCH request:

axios
    .patch('http://localhost:8090/api/characters/1', { bookId: 1, name: 'Bob'})
    .then(function () { /*...*/ })
    .catch(function() { /*...*/ });

Here's the raw request:

PATCH http://localhost:8090/api/characters/6 HTTP/1.1
Host: localhost:8090
Connection: keep-alive
Content-Length: 30
Accept: application/json, text/plain, */*
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: http://localhost:3000/library/book/2/character/6
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-AU,en;q=0.8,ru;q=0.6

{"bookId":1,"name":"Bob"}

My ViewModel:

public class UpdateCharacterViewModel
{
    public string Name { get; set; }
}

And finally, the Web API action:

[Route("~/api/[controller]/{characterId}")]
[HttpPatch]
public IActionResult Update(int characterId, [FromBody]UpdateCharacterViewModel viewModel, [FromBody]JsonPatchDocument<UpdateCharacterViewModel> patch)
{
    // viewModel is bound correctly but patch is NULL
    // ...
}

I'm finding that patch comes through as NULL, indicating there's an issue with binding. To check there wasn't issues with the request, I added the viewModel and find that it binds correctly - a populated UpdateCharacterViewModel is available to the action.

What am I doing wrong here?

1 Answer 1

10

Ahh, oops. It looks like the request data needs to be in a certain format, whereas I mistakenly thought that the patch was implicit based on the properties that were or were not included in the request's data.

Here's an example of what the request should look like to work properly with JsonPatchDocument:

PATCH /api/characters/1
[
    {
      "op": "replace",
      "path": "/name",
      "value": "Bob"
    }
]

Thankfully there are a few libraries out there that make creating this patch data easy. JSON-patch seems to be a good one. You can either generate the patch data by observing changes to an object:

var myobj = { firstName:"Joachim", lastName:"Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
observer = jsonpatch.observe( myobj );
myobj.firstName = "Albert";
myobj.contactDetails.phoneNumbers[0].number = "123";
myobj.contactDetails.phoneNumbers.push({number:"456"});
var patches = jsonpatch.generate(observer);
// patches  == [
//   { op:"replace", path="/firstName", value:"Albert"},
//   { op:"replace", path="/contactDetails/phoneNumbers/0/number", value:"123"},
//   { op:"add", path="/contactDetails/phoneNumbers/1", value:{number:"456"}}];

Or alteratively you can run a diff between two objects:

var objA = {user: {firstName: "Albert", lastName: "Einstein"}};
var objB = {user: {firstName: "Albert", lastName: "Collins"}};
var diff = jsonpatch.compare(objA, objB));
//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}]

Lastly, be wary of my attempts at debugging the API controller action. According to the answer to this question, you can only decorate one parameter with the [FromBody] attribute. All subsequent parameters may not be bound!

Sign up to request clarification or add additional context in comments.

1 Comment

This would have taken me forever to realize I needed the patch operations to be in an array. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.