Ich habe mich in letzter Zeit mit CQRS / MediatR befasst. Aber je mehr ich einen Drilldown durchführe, desto weniger mag ich es. Vielleicht habe ich etwas / alles falsch verstanden.
Es fängt also großartig an, wenn Sie behaupten, Ihren Controller darauf zu reduzieren
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Das passt perfekt zur Richtlinie für dünne Controller. Es werden jedoch einige ziemlich wichtige Details ausgelassen - die Fehlerbehandlung.
Sehen wir uns die Standardaktion Loginaus einem neuen MVC-Projekt an
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);
}
Das Konvertieren stellt uns vor eine Reihe von Problemen in der realen Welt. Denken Sie daran, das Ziel ist es, es zu reduzieren
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Eine mögliche Lösung besteht darin, ein CommandResult<T>anstelle von a zurückzugeben modelund dann den CommandResultFilter in einer Nachaktion zu behandeln. Wie hier besprochen .
Eine Implementierung von CommandResultkönnte so sein
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Dies löst jedoch unser Problem in der LoginAktion nicht wirklich , da es mehrere Fehlerzustände gibt. Wir könnten diese zusätzlichen Fehlerzustände hinzufügen, ICommandResultaber das ist ein guter Anfang für eine sehr aufgeblähte Klasse / Schnittstelle. Man könnte sagen, es entspricht nicht der Single Responsibility (SRP).
Ein weiteres Problem ist das returnUrl. Wir haben diesen return RedirectToLocal(returnUrl);Code. Irgendwie müssen wir bedingte Argumente behandeln, die auf dem Erfolgsstatus des Befehls basieren. Ich glaube zwar, dass dies getan werden könnte (ich bin nicht sicher, ob der ModelBinder FromBody- und FromQuery- returnUrlArgumente ( is FromQuery) einem einzelnen Modell zuordnen kann ). Man kann sich nur fragen, welche verrückten Szenarien die Straße runterkommen könnten.
Die Modellvalidierung ist zusammen mit der Rückgabe von Fehlermeldungen auch komplexer geworden. Nehmen Sie dies als Beispiel
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Wir fügen dem Modell eine Fehlermeldung bei. Mit einer ExceptionStrategie (wie hier vorgeschlagen ) kann so etwas nicht gemacht werden, weil wir das Modell brauchen. Vielleicht können Sie das Modell aus dem Requestherunterladen, aber es wäre ein sehr komplizierter Prozess.
Alles in allem fällt es mir schwer, diese "einfache" Aktion umzusetzen.
Ich suche nach Eingaben. Bin ich hier total im Unrecht?