10. Updating Data (PUT & PATCH)

  1. 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)