Ultimamente ho esaminato CQRS / MediatR. Ma più approfondisco meno mi piace. Forse ho frainteso qualcosa / tutto.
Quindi inizia impressionante affermando di ridurre il controller a questo
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Che si adatta perfettamente alla linea guida del controller sottile. Tuttavia, lascia alcuni dettagli piuttosto importanti: la gestione degli errori.
Osserviamo l'azione predefinita Login
da un nuovo progetto 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);
}
Conversione che ci presenta una serie di problemi del mondo reale. Ricorda che l'obiettivo è ridurlo a
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Una possibile soluzione a questo è restituire CommandResult<T>
anziché model
e quindi gestire CommandResult
in un filtro post-azione. Come discusso qui .
Un'implementazione di CommandResult
potrebbe essere come questa
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Tuttavia ciò non risolve il nostro problema nell'azione Login
, perché ci sono più stati di errore. Potremmo aggiungere questi stati di errore extra a ICommandResult
ma questo è un ottimo inizio per una classe / interfaccia molto gonfia. Si potrebbe dire che non è conforme alla Single Responsibility (SRP).
Un altro problema è returnUrl
. Abbiamo questo return RedirectToLocal(returnUrl);
pezzo di codice. In qualche modo dobbiamo gestire gli argomenti condizionali in base allo stato di successo del comando. Mentre penso che si possa fare (non sono sicuro che ModelBinder possa mappare gli argomenti FromBody e FromQuery ( returnUrl
FromQuery) su un singolo modello). Ci si può solo chiedere che tipo di scenari pazzi potrebbero venire giù per la strada.
La convalida del modello è diventata anche più complessa insieme alla restituzione dei messaggi di errore. Prendi questo come un esempio
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Alleghiamo un messaggio di errore insieme al modello. Questo tipo di cose non può essere fatto usando una strategia Exception
(come suggerito qui ) perché abbiamo bisogno del modello. Forse puoi ottenere il modello da Request
ma sarebbe un processo molto complicato.
Quindi, tutto sommato, sto facendo fatica a convertire questa azione "semplice".
Sto cercando input. Sono totalmente nel torto qui?