Ostatnio badałem CQRS / MediatR. Ale im więcej drążę, tym mniej mi się podoba. Być może źle zrozumiałem coś / wszystko.
Zaczyna się więc niesamowicie, twierdząc, że zredukowałeś do tego kontroler
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Co idealnie pasuje do cienkich wytycznych kontrolera. Pomija jednak kilka bardzo ważnych szczegółów - obsługę błędów.
Przyjrzyjmy się domyślnej Login
akcji z nowego projektu 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);
}
Konwersja, która stwarza nam wiele problemów w świecie rzeczywistym. Pamiętaj, że celem jest ograniczenie tego do
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Jednym z możliwych rozwiązań jest zwrócenie CommandResult<T>
zamiast a, model
a następnie obsłużenie CommandResult
filtru po działaniu. Jak omówiono tutaj .
Jedna implementacja CommandResult
może być taka
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Jednak to tak naprawdę nie rozwiązuje naszego problemu w Login
działaniu, ponieważ istnieje wiele stanów awarii. Możemy dodać te dodatkowe stany awarii, ICommandResult
ale jest to świetny początek dla bardzo rozdętej klasy / interfejsu. Można powiedzieć, że nie jest on zgodny z Single Responsibility (SRP).
Innym problemem jest returnUrl
. Mamy ten return RedirectToLocal(returnUrl);
fragment kodu. Jakoś musimy obsługiwać argumenty warunkowe w oparciu o stan powodzenia polecenia. Chociaż myślę, że można to zrobić (nie jestem pewien, czy ModelBinder może zmapować argumenty FromBody i FromQuery ( returnUrl
jest FromQuery) na pojedynczy model). Można się tylko zastanawiać, jakie szalone scenariusze mogą przyjść na później.
Sprawdzanie poprawności modelu stało się również bardziej złożone wraz ze zwracaniem komunikatów o błędach. Weź to jako przykład
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Do modelu dołączamy komunikat o błędzie. Tego rodzaju rzeczy nie można zrobić przy użyciu Exception
strategii (jak sugerowano tutaj ), ponieważ potrzebujemy modelu. Być może możesz pobrać model z, Request
ale byłby to bardzo zaangażowany proces.
Podsumowując, trudno mi było przekonwertować tę „prostą” akcję.
Szukam danych wejściowych. Czy tutaj całkowicie się mylę?