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' Login
action 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 CommandResult
filtre dans un post-action. Comme discuté ici .
Une implémentation du CommandResult
pourrait ê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' Login
action, car il existe plusieurs états de défaillance. Nous pourrions ajouter ces états d'échec supplémentaires, ICommandResult
mais 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 ( returnUrl
est 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 Exception
stratégie (comme suggéré ici ) parce que nous avons besoin du modèle. Vous pouvez peut-être obtenir le modèle de la Request
mais 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?