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 Loginacció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 modely luego manejarlo CommandResulten un filtro posterior a la acción. Como se discutió aquí .
Una implementación de la CommandResultpodrí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 Loginacción, porque hay múltiples estados de falla. Podríamos agregar estos estados de falla adicionales, ICommandResultpero 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 ( returnUrles 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 Exceptionestrategia (como se sugiere aquí ) porque necesitamos el modelo. Quizás pueda obtener el modelo del, Requestpero 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í?