I'm moving my logic from Controller to CQRS Handlers and I'm not sure how to deal with code that calls ControllerBase stuff like:
[HttpPut("users/{id}")]
public IActionResult Put(UserForm userForm)
{
var user = Users.Get(email);
if (user == null)
return NotFound();
...
}
So when I move this logic outside action, obviously I don't have access to NotFound
anymore. I see a number of ways to solve it:
I quick Google search was not successful so may be I'm missing something. What's the recommended approach to handle a situation like this?
This logic/validation doesn't belong to Command/Query handlers, as it's handling different things (user input -> BadRequest, resource status (NotFound, Ok, Created (201))) for certain scenarios (WebAPI, MVC, Desktop, Mobile App etc.) and the responses highly depend on the infrastructure (in REST you'd return status code, MVC redirecting, Desktop messages, etc.)
Using exceptions is unacceptable, since these are common scenarios and not exceptional ones (at least input validation and "not found"). And using IActionResult
within handlers is tight coupling to infrastructure too.
In bigger projects I use "result" classes, similar to IdentityResult
in ASP.NET Core Identity.
namespace MyProject.Common
{
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
}
And return these from commands (QueryResult
) or queries (QueryResult<T>
).
You can then write extension methods which return the correct IActionResult
.
public static class QueryResultExtensions
{
public IActionResult ToActionResult(this QueryResult result)
{
if(result.Success)
{
return new OkResult();
}
if(result.Errors != null)
{
// ToModelStateDictionary needs to be implemented by you ;)
var errors = result.Errors.ToModelStateDictionary();
return BadRequestResult(errors);
}
return BadRequestResult();
}
public IActionResult ToActionResult<T>(this QueryResult<T> result)
{
if(result.Success)
{
if(result.Result == null)
{
return new NotFoundResult();
}
return new OkObjectResult(result.Result);
}
if(result.Errors != null)
{
// ToModelStateDictionary needs to be implemented by you ;)
var errors = result.Errors.ToModelStateDictionary();
return BadRequestResult(errors);
}
return BadRequestResult();
}
}
Then just call it in your controller
[HttpPut("users/{id}")]
public IActionResult Put(CreateUserCommand command)
{
var result = commandHandler.Handle(command);
return result.ToActionResult();
}
You lose the ability to have a more fine-grained ability to control return codes. You can create different Result types for commands/queries or different extension methods which handle it differently .ToCreateActionResult()
, .ToQueryActionResult()
, etc.