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 Login
aus 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 model
und dann den CommandResult
Filter in einer Nachaktion zu behandeln. Wie hier besprochen .
Eine Implementierung von CommandResult
könnte so sein
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Dies löst jedoch unser Problem in der Login
Aktion nicht wirklich , da es mehrere Fehlerzustände gibt. Wir könnten diese zusätzlichen Fehlerzustände hinzufügen, ICommandResult
aber 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- returnUrl
Argumente ( 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 Exception
Strategie (wie hier vorgeschlagen ) kann so etwas nicht gemacht werden, weil wir das Modell brauchen. Vielleicht können Sie das Modell aus dem Request
herunterladen, 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?