I have used PUT/POST to update the applications I'm working with, which has been straightforward until now. Now I'm working on an API that should expose a way to update data in a database, and my initial thought was that I could replace the row (or replace it partially). Since different applications with different knowledge about the data need to update, I think it could be a good idea with a PATCH action instead of a PUT.
I can find some toy examples that implement the update in the controller, which I'm not interested in.
I use the same pattern(CQRS w. Mediatr) as in the Clean architecture project by jasontaylordev , so i will use that in my example.
I have two questions.
I don't like that the JsonPatchDocument
object should reach my Handlers, but I don't know how I can avoid that. Access to the database from the controllers is not an option. So are there other options? (n.b. Automapper or similar dynamic mapping libraries is neither an option in the projects I'm working on)
How can I build up the JsonPatch objects from the client applications (all dotnet core projects). I think it could be neat if I could have a DTO in application one that has the two properties Title
and Note
and if Title="test"
and Note=null
, then it should replace the Title and delete the Note. Can I map to the (weird) patch object in an easy way?
[HttpPatch("{id}")]
public async Task<ActionResult> Update(int id, UpdateTodoItemCommand command)
{
if (id != command.Id)
{
return BadRequest();
}
await Mediator.Send(command);
return NoContent();
}
public class UpdateTodoItemCommand : IRequest
{
public int Id { get; set; }
public JsonPatchDocument<TodoItemDto> Todo { get; set; }
}
public class TodoItemDto
{
public string Title { get; set; }
public string Note { get; set; }
}
public class UpdateTodoItemCommandHandler : IRequestHandler<UpdateTodoItemCommand>
{
private readonly IApplicationDbContext _context;
public UpdateTodoItemCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await _context.TodoItems.FindAsync(request.Id);
if (entity == null)
{
throw new NotFoundException(nameof(TodoItem), request.Id);
}
var todoItemDto = new TodoItemDto();
todoItemDto.Title = entity.Title;
todoItemDto.Note = entity.Note:
todoItemDto.Todo.ApplyTo(request.Todo);
entity.Title = todoItemDto.Title;
entity.Note = todoItemDto.Note;
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
I had the same problem and I came up with a solution that worked for me.
In the Handle method of you UpdateTodoItemCommandHandler class you would create one more JsonPatchDocument, but of type TodoItem (not Dto). And then transfer the operations and ContractResolver to your new JsonPatchDocument object, which you can patch and save to the database. Something like this:
var entity = await _context.TodoItems.FindAsync(request.Id); if (entity == null) { throw new NotFoundException(nameof(TodoItem), request.Id); } var todoItemPatch = new JsonPatchDocument<TodoItem>(); foreach (var opr in request.Todo.Operations) { todoItemPatch.Operations.Add(new Operation<TodoItem> { op = opr.op, path = opr.path, value = opr.value }); } todoItemPatch.ContractResolver = request.Todo.ContractResolver; todoItemPatch.ApplyTo(entity); await _context.SaveChangesAsync(cancellationToken);