Ultimamente tenho pesquisado o CQRS / MediatR. Mas quanto mais eu detalhar, menos eu gosto. Talvez eu tenha entendido mal alguma coisa / tudo.
Então, ele começa incrível, alegando reduzir seu controlador a esse
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
O que se encaixa perfeitamente com as diretrizes finas do controlador. No entanto, deixa de fora alguns detalhes muito importantes - tratamento de erros.
Vamos analisar a Login
ação padrão de um novo projeto MVC
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Convertendo isso nos apresenta um monte de problemas do mundo real. Lembre-se que o objetivo é reduzi-lo a
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Uma solução possível para isso é retornar um em CommandResult<T>
vez de um model
e depois manipular o CommandResult
filtro em uma ação pós. Como discutido aqui .
Uma implementação do CommandResult
poderia ser assim
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
No entanto, isso realmente não resolve o nosso problema na Login
ação, porque existem vários estados de falha. Poderíamos adicionar esses estados extras de falha, ICommandResult
mas isso é um ótimo começo para uma classe / interface muito inchada. Pode-se dizer que não está de acordo com a responsabilidade única (SRP).
Outro problema é o returnUrl
. Nós temos esse return RedirectToLocal(returnUrl);
pedaço de código. De alguma forma, precisamos lidar com argumentos condicionais com base no estado de sucesso do comando. Embora eu ache que isso possa ser feito (não tenho certeza se o ModelBinder pode mapear os argumentos FromBody e FromQuery ( returnUrl
é FromQuery) para um único modelo). Só podemos imaginar que tipo de cenários malucos poderiam surgir no caminho.
A validação do modelo também se tornou mais complexa junto com o retorno de mensagens de erro. Tome isso como um exemplo
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Anexamos uma mensagem de erro junto com o modelo. Esse tipo de coisa não pode ser feito usando uma Exception
estratégia (como sugerido aqui ) porque precisamos do modelo. Talvez você possa obter o modelo do Request
mas seria um processo muito envolvido.
Então, apesar de tudo, estou tendo dificuldade para converter essa ação "simples".
Eu estou procurando por entradas. Estou totalmente errado aqui?