About Patch Verb in REST
I am enhancing a Web API and I heard about Patch capabilities, but I didn't really had my hands on it.
I used Perpexity AI to have an analysis of the next method,
and I find the info provided very interesting
Here is the Perplexity text:
Thinking in terms of your code, PATCH is basically “apply a
list of small operations to this object,” instead of “send me the whole object
and I’ll overwrite it.”
What PATCH means conceptually
- PATCH
is for partial updates.
Example: Your User has DisplayName, Email, PasswordHash, etc. With PATCH you can say: “only change DisplayName” and not touch the rest.[1] - The
client sends an instruction document (JSON Patch) that describes
operations like “replace this property with that value,” “add this value,”
“remove this field,” etc., instead of sending a full User.[2][3]
- The
server:
So PATCH is “delta-based,” while PUT is “state-based.”
JSON Patch format in practice
You are using JsonPatchDocument<UserDto>. That
corresponds to a JSON Patch document in the body, something like:
- Replace
a field:
- [{
"op": "replace", "path":
"/displayName", "value": "New Name" }]
- Add
something:
- [{
"op": "add", "path":
"/someProperty", "value": "X" }]
- Remove
something:
- [{
"op": "remove", "path":
"/someProperty" }]
Key points:
- op:
one of add, remove, replace, copy, move, test. In your user scenario
you’ll usually use replace.[3]
- path:
JSON Pointer to the property, based on your DTO: /displayName, /email,
etc.
- value:
the new value (for add/replace).
Your controller action is the standard pattern for this
approach.[1][2]
How your PatchUser flow works
Given your method:
- patchDoc
null-check
- If
there is no patch document, it’s a bad request.
- Load
entity
- var
user = await _userService.GetUserFromIdAsync(id);
- If
null → NotFound().
- Map
entity → DTO
- var
userDto = user.ToDto(_tokenService);
- Now
you have a mutable object that represents what can be exposed/edited.
- Apply
patch
- patchDoc.ApplyTo(userDto,
ModelState);
- This
walks through operations (replace / add / remove) and modifies the DTO in
memory.
- If a
path is wrong or a value type is invalid, it adds errors into ModelState.[3][2]
- Validate
- if
(!TryValidateModel(userDto)) { return ValidationProblem(ModelState); }
- This
runs data annotations / validation attributes on your DTO after the
patch.
- This
is important because PATCH may create invalid combinations (e.g., empty
email).
- Persist
- await
_userService.UpdateUserAsync(userDto);
- Here
you load the entity again (by Id), update the allowed fields from userDto
(e.g., DisplayName), and save.
- Return
- return
NoContent(); for successful partial update.
So: controller = orchestration + validation, service =
“actually update the entity with these new values.”
How to implement UpdateUserAsync safely for PATCH
You want the service to:
- Load
the entity by Id.
- Update
only the allowed properties.
- Save.
For example:
- If
you only allow changing DisplayName:
- user.DisplayName
= userDto.DisplayName ?? user.DisplayName;
- If
you later allow more fields, add more lines for each allowed field.
- Do
not touch sensitive fields like PasswordHash, PasswordSalt, Email unless
you explicitly decide so.
You can think of it as: “Take the patched DTO and use it as
a source of truth only for specific properties.”
When to choose PATCH for your Users
For your scenario, PATCH is a very good fit when:
- You
have a user profile where only a few fields change at a time (e.g.,
DisplayName, maybe avatar, etc.).
- You
don’t want clients to send the whole user object (and risk accidentally
overwriting email or other sensitive data).[1]
Practically:
- You
keep PUT for “full replace” scenarios (if you ever need them).
- You
use PATCH for “edit profile” type endpoints like the one you just built.
If you want, next step can be:
- Designing
the exact properties that should be patchable in UserDto (e.g., allow
DisplayName, maybe some profile fields, but not Email/Password), and
writing a concrete UpdateUserAsync implementation with those rules.
In your PATCH method, these two lines play a critical role
in applying and validating the partial updates coming from the client:
patchDoc.ApplyTo(userDto, ModelState);
if (!TryValidateModel(userDto))
{
return
ValidationProblem(ModelState);
}
What patchDoc.ApplyTo(userDto, ModelState) does
- patchDoc
is of type JsonPatchDocument<UserDto>, representing a list of JSON
Patch operations sent by the client, like "replace DisplayName with
'NewName'".
- The ApplyTo
method applies these patch operations directly onto the target object,
here userDto. It programmatically modifies the properties of userDto as
per the patch instructions.
- The
second parameter, ModelState, is a dictionary provided by ASP.NET Core to collect errors during model
binding and validation.
- If
any patch operation is invalid—such as an attempt to patch a non-existent
property, an invalid value type, or violates constraints—ApplyTo adds
descriptive errors into ModelState.
- This
delegated error collection mechanism allows the controller to distinguish
between successful and erroneous PATCH payloads during application.
What TryValidateModel(userDto) does after patching
- After
successfully or partially applying patch operations, you run full model
validation on the updated userDto.
- This
method performs validation according to data annotations and other
configured validators on the DTO's properties.
- For
example, if after patch application the DisplayName became empty but
marked [Required], validation will fail.
- If
validation fails, TryValidateModel returns false.
- At
that point, you return a detailed validation problem response (ValidationProblem(ModelState)),
which includes all errors gathered both from patch application and
validation phases.
Why these steps are important
- You
cannot just blindly apply patch operations without error checking; invalid
patch data must be caught and reported.
- Applying
patch operations can produce an invalid state in your DTO that must be
validated before persisting.
- Using
ModelState as a shared error container lets both patch application and
validation errors be handled uniformly.
- This
pattern is the standard recommended approach in ASP.NET
Core for PATCH support ensuring robustness and meaningful error reporting
to API consumers.
Summary
- patchDoc.ApplyTo(userDto,
ModelState): applies patch changes and logs errors.
- TryValidateModel(userDto):
checks the patched object meets your validation rules.
- If
validation fails, your API responds with detailed validation errors,
preventing bad data saving.
This two-step approach protects your API from invalid patch
requests and helps clients understand and correct request issues.
⁂
- https://dotnettutorials.net/lesson/http-patch-method-in-asp-net-core-web-api/
- https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-10.0
- https://www.catchpoint.com/blog/applying-json-patch-operations
- https://learn.microsoft.com/es-es/aspnet/core/web-api/jsonpatch?view=aspnetcore-10.0
- https://stackoverflow.com/questions/70668079/how-to-implement-httppatch-action-method-in-asp-net-core-6-web-api-without-using
- https://www.reddit.com/r/dotnet/comments/1l8cskm/how_to_implement_http_patch_with/
- https://www.c-sharpcorner.com/article/json-patch-2-in-asp-net-core-web-api-for-database/
- https://hamidmosalla.com/2018/04/14/asp-net-core-api-patch-method-without-using-jsonpatchdocument/
- https://www.youtube.com/watch?v=RuvG5C8axw8
Comments
Post a Comment