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

 

A screen shot of a computer program

AI-generated content may be incorrect.

 

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:
    1. Loads the current entity (User) from the DB.
    2. Converts it to a DTO (your UserDto).
    3. Applies the patch operations to that DTO.
    4. Validates the patched DTO.
    5. Maps the changes back to the entity and saves.[2][1]

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:

  1. patchDoc null-check
    • If there is no patch document, it’s a bad request.
  2. Load entity
    • var user = await _userService.GetUserFromIdAsync(id);
    • If null → NotFound().
  3. Map entity → DTO
    • var userDto = user.ToDto(_tokenService);
    • Now you have a mutable object that represents what can be exposed/edited.
  4. 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]
  5. 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).
  6. Persist
    • await _userService.UpdateUserAsync(userDto);
    • Here you load the entity again (by Id), update the allowed fields from userDto (e.g., DisplayName), and save.
  7. 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.

 

 

  1. https://dotnettutorials.net/lesson/http-patch-method-in-asp-net-core-web-api/   
  2. https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-10.0   
  3. https://www.catchpoint.com/blog/applying-json-patch-operations  
  4. https://learn.microsoft.com/es-es/aspnet/core/web-api/jsonpatch?view=aspnetcore-10.0
  5. https://stackoverflow.com/questions/70668079/how-to-implement-httppatch-action-method-in-asp-net-core-6-web-api-without-using
  6. https://www.reddit.com/r/dotnet/comments/1l8cskm/how_to_implement_http_patch_with/
  7. https://www.c-sharpcorner.com/article/json-patch-2-in-asp-net-core-web-api-for-database/
  8. https://hamidmosalla.com/2018/04/14/asp-net-core-api-patch-method-without-using-jsonpatchdocument/
  9. https://www.youtube.com/watch?v=RuvG5C8axw8


Comments

Popular Posts