Нещодавно я розглядав CQRS / MediatR. Але чим більше я набиваю, тим менше мені подобається. Можливо, я щось / все зрозумів неправильно.
Отже, це починається приголомшливо, стверджуючи, що зводити контролер до цього
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Що ідеально підходить до тонкої настанови контролера. Однак він залишає деякі досить важливі деталі - поводження з помилками.
Розглянемо Login
дії за замовчуванням у новому проекті 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);
}
Перетворення, яке представляє нам купу проблем у реальному світі. Пам'ятайте, мета - звести її до
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Одне з можливих рішень для цього - повернути CommandResult<T>
замість а, model
а потім обробити CommandResult
фільтр після дії. Як обговорювалося тут .
Одне виконання CommandResult
може бути таким
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Однак це насправді не вирішує нашу проблему в Login
дії, оскільки існує кілька станів відмови. Ми могли б додати ці додаткові стани відмов, ICommandResult
але це чудовий початок для дуже роздутого класу / інтерфейсу. Можна сказати, що він не відповідає єдиній відповідальності (SRP).
Ще одна проблема - це returnUrl
. У нас є цей return RedirectToLocal(returnUrl);
фрагмент коду. Якось нам потрібно обробити умовні аргументи на основі стану успішності команди. Хоча я думаю, що це можна зробити (я не впевнений, чи ModelBinder може зіставити аргументи FromBody та FromQuery ( returnUrl
є FromQuery) в одну модель). Можна лише дивуватися, які шалені сценарії можуть зійти на дорогу.
Перевірка моделей також стала більш складною разом із поверненнями повідомлень про помилки. Візьмемо це як приклад
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Ми додаємо повідомлення про помилку разом із моделлю. Такого роду неможливо зробити за допомогою Exception
стратегії (як тут запропоновано ), оскільки нам потрібна модель. Можливо, ви можете отримати модель від цього, Request
але це був би дуже задіяний процес.
Так що загалом мені важко перетворити цю "просту" дію.
Я шукаю вхідні дані. Я тут абсолютно не так?