10. Updating Data (PUT & PATCH)
- Updating Data (PUT & PATCH)
About this chapter
Implement update operations using PUT for full replacements and PATCH for partial updates with proper idempotency and error handling.
- PUT semantics: Full resource replacement and idempotency
- PATCH operations: Partial updates with JSON Patch documents
- HTTP 204 No Content: Appropriate response for successful updates
- Resource validation: Checking existence before updates
- Change tracking: EF Core’s automatic change detection
- Conflict handling: Managing concurrent updates and optimization
Learning outcomes:
- Implement PUT endpoints for full resource updates
- Use PATCH with JsonPatchDocument for partial updates
- Understand idempotency in update operations
- Return appropriate status codes (204, 404, 400)
- Apply AutoMapper for DTO-to-entity mapping
- Handle optimistic concurrency conflicts
10.1 Full Updates with PUT
[HttpPut("{id}")][ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]public async Task<ActionResult> UpdatePlatform(int id, PlatformMutateDto platformDto)
{
// Verify resource exists
var platformFromRepo = await _repository.GetPlatformByIdAsync(id);
if (platformFromRepo == null)
{
return NotFound(new { message = $"Platform with ID {id} not found" });
}
// Map DTO properties to entity
_mapper.Map(platformDto, platformFromRepo);
// Update (might be no-op due to change tracking)
await _repository.UpdatePlatformAsync(platformFromRepo);
await _repository.SaveChangesAsync();
return NoContent(); // 204 - successful, no content to return}
- PUT Semantics: Replace entire resource
- 204 No Content: Standard response for successful update
- Idempotent: Multiple identical PUT requests = same result
10.2 Partial Updates with JSON Patch
[HttpPatch("{id}")]
[Consumes("application/json-patch+json")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PartialUpdate(
int id,
JsonPatchDocument<CommandMutateDto> patchDoc)
{
var commandFromRepo = await _repository.GetCommandByIdAsync(id);
if (commandFromRepo == null)
{
return NotFound();
}
// Map entity to DTO
var commandToPatch = _mapper.Map<CommandMutateDto>(commandFromRepo);
// Apply patch operations
patchDoc.ApplyTo(commandToPatch, ModelState);
// Validate patched DTO
if (!TryValidateModel(commandToPatch))
{
return BadRequest(ModelState);
}
// Map back to entity
_mapper.Map(commandToPatch, commandFromRepo);
// Validate foreign keys
var platform = await _repository.GetPlatformByIdAsync(commandFromRepo.PlatformId);
if (platform == null)
{
return NotFound(new { message = "Invalid platform ID" });
}
await _repository.UpdateCommandAsync(commandFromRepo);
await _repository.SaveChangesAsync();
return NoContent();}
10.3 Understanding JsonPatchDocument
### PATCH Request Example
PATCH {{baseUrl}}/api/commands/1
Content-Type: application/json-patch+json
x-api-key: {{apiKey}}
[
{
"op": "replace",
"path": "/howTo",
"value": "Updated description"
},
{
"op": "replace",
"path": "/platformId",
"value": 2
}
]
- Patch Operations:
- add: Add value to array or object
- remove: Remove property or array element
- replace: Change value
- move: Move value from one path to another
- copy: Copy value to another path
- test: Test that value matches (precondition)
- Why JSON Patch:
- Bandwidth efficiency (only send changes)
- Explicit operations
- Standard format (RFC 6902)
10.4 CORRECTION: Fixing Typos in Error Messages
// ❌ WRONG (current code has typos)
return NotFound("You mus supply a valid commandId in the route");
// ✅ CORRECTreturn NotFound(new
{
message = "You must supply a valid command ID in the route",
parameterName = "id",
providedValue = id
});
// Even better with structured error response
return NotFound(new ProblemDetails
{
Status = StatusCodes.Status404NotFound,
Title = "Command not found",
Detail = $"No command exists with ID {id}",
Instance = HttpContext.Request.Path
});
- Professional Error Messages: Complete sentences, proper grammar
- Helpful Context: Include what went wrong and what was expected
10.5 Validation During Updates
// Validate patched model
if (!TryValidateModel(commandToPatch))
{
return BadRequest(ModelState);
}
// Validate business rules
if (commandToPatch.PlatformId != commandFromRepo.PlatformId)
{
var newPlatform = await _repository.GetPlatformByIdAsync(commandToPatch.PlatformId);
if (newPlatform == null)
{
ModelState.AddModelError(
nameof(commandToPatch.PlatformId),
$"Platform ID {commandToPatch.PlatformId} does not exist");
return BadRequest(ModelState);
}
}
- Two-Phase Validation:
- Model validation (data annotations)
- Business rule validation (database constraints, logic)