Je me suis penché récemment sur CQRS / MediatR. Mais plus j'explore moins je l'aime. J'ai peut-être mal compris quelque chose / tout.
Cela commence donc génial en prétendant réduire votre contrôleur à ce
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Ce qui correspond parfaitement à la ligne directrice du contrôleur mince. Cependant, il omet certains détails assez importants - la gestion des erreurs.
Regardons l' Loginaction par défaut d'un nouveau projet 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);
}
La conversion qui nous présente un tas de problèmes du monde réel. N'oubliez pas que l'objectif est de le réduire à
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Une solution possible à cela consiste à renvoyer un CommandResult<T>au lieu d'un model, puis à gérer le CommandResultfiltre dans un post-action. Comme discuté ici .
Une implémentation du CommandResultpourrait être comme ceci
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Cependant, cela ne résout pas vraiment notre problème dans l' Loginaction, car il existe plusieurs états de défaillance. Nous pourrions ajouter ces états d'échec supplémentaires, ICommandResultmais c'est un bon début pour une classe / interface très gonflée. On pourrait dire qu'il n'est pas conforme à la responsabilité unique (SRP).
Un autre problème est le returnUrl. Nous avons ce return RedirectToLocal(returnUrl);morceau de code. D'une manière ou d'une autre, nous devons gérer les arguments conditionnels basés sur l'état de réussite de la commande. Bien que je pense que cela pourrait être fait (je ne sais pas si le ModelBinder peut mapper les arguments FromBody et FromQuery ( returnUrlest FromQuery) à un seul modèle). On ne peut que se demander quel genre de scénarios fous pourraient se produire sur la route.
La validation des modèles est également devenue plus complexe avec le renvoi de messages d'erreur. Prenez cela comme exemple
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Nous joignons un message d'erreur avec le modèle. Ce genre de chose ne peut pas être fait en utilisant une Exceptionstratégie (comme suggéré ici ) parce que nous avons besoin du modèle. Vous pouvez peut-être obtenir le modèle de la Requestmais ce serait un processus très complexe.
Donc, dans l'ensemble, j'ai du mal à convertir cette action "simple".
Je cherche des entrées. Suis-je totalement dans l'erreur ici?