Reputation: 9582
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?
Upvotes: 4
Views: 5760
Reputation: 9582
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!
Upvotes: 10