He estado buscando en CQRS / MediatR últimamente. Pero cuanto más profundizo, menos me gusta. Quizás he entendido mal algo / todo.
Por lo tanto, comienza increíble al afirmar que reduce su controlador a esto
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Que encaja perfectamente con la guía del controlador delgado. Sin embargo, deja de lado algunos detalles bastante importantes: manejo de errores.
Veamos la Login
acción predeterminada de un nuevo proyecto 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);
}
Conversión que nos presenta un montón de problemas del mundo real. Recuerde que el objetivo es reducirlo a
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Una posible solución a esto es devolver un en CommandResult<T>
lugar de a model
y luego manejarlo CommandResult
en un filtro posterior a la acción. Como se discutió aquí .
Una implementación de la CommandResult
podría ser así
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Sin embargo, eso realmente no resuelve nuestro problema en la Login
acción, porque hay múltiples estados de falla. Podríamos agregar estos estados de falla adicionales, ICommandResult
pero ese es un gran comienzo para una clase / interfaz muy hinchada. Se podría decir que no cumple con la responsabilidad única (SRP).
Otro problema es el returnUrl
. Tenemos este return RedirectToLocal(returnUrl);
pedazo de código. De alguna manera, necesitamos manejar argumentos condicionales basados en el estado de éxito del comando. Si bien creo que podría hacerse (no estoy seguro de si ModelBinder puede asignar argumentos FromBody y FromQuery ( returnUrl
es FromQuery) a un solo modelo). Uno solo puede preguntarse qué tipo de escenarios locos podrían venir en el futuro.
La validación del modelo también se ha vuelto más compleja junto con la devolución de mensajes de error. Toma esto como un ejemplo
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Adjuntamos un mensaje de error junto con el modelo. Este tipo de cosas no se pueden hacer usando una Exception
estrategia (como se sugiere aquí ) porque necesitamos el modelo. Quizás pueda obtener el modelo del, Request
pero sería un proceso muy complicado.
Así que, en general, me está costando convertir esta acción "simple".
Estoy buscando entradas. ¿Estoy totalmente equivocado aquí?