在我们的申请中,我们有CQRS:我们IAsyncCommand
与IAsyncCommandHandler<IAsyncCommand>
。
通常命令通过Mediator处理,如下所示:
var mediator = //get mediator injected into MVC controller via constructor
var asyncCommand = // construct AsyncCommand
// mediator runs ICommandValidator and that returns a list of errors if any
var errors = await mediator.ProcessCommand(asyncCommand);
工作正常。现在我注意到我在控制器操作中做了很多重复的代码:
public async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command)
{
if (!ModelState.IsValid)
{
return View(command);
}
var result = await mediator.ProcessCommandAsync(command);
if (!result.IsSuccess())
{
AddErrorsToModelState(result);
return View(command);
}
return RedirectToAction(MVC.HomePage.Index());
}
这种模式在许多控制器中反复重复。因此,对于单线程命令,我已经完成了简化:
public class ProcessCommandResult<T> : ActionResult where T : ICommand
{
private readonly T command;
private readonly ActionResult failure;
private readonly ActionResult success;
private readonly IMediator mediator;
public ProcessCommandResult(T command, ActionResult failure, ActionResult success)
{
this.command = command;
this.success = success;
this.failure = failure;
mediator = DependencyResolver.Current.GetService<IMediator>();
}
public override void ExecuteResult(ControllerContext context)
{
if (!context.Controller.ViewData.ModelState.IsValid)
{
failure.ExecuteResult(context);
return;
}
var handlingResult = mediator.ProcessCommand(command);
if (handlingResult.ConainsErrors())
{
AddErrorsToModelState(handlingResult);
failure.ExecuteResult(context);
}
success.ExecuteResult(context);
}
// plumbing code
}
完成一些管道后,我的控制器操作如下所示:
public virtual ActionResult Create(DoStuffCommand command)
{
return ProcessCommand(command, View(command), RedirectToAction(MVC.HomePage.Index()));
}
这适用于我不需要执行async-await
模式的sync-commands。一旦我尝试执行async
操作,这就不能编译,因为MVC中没有AsyncActionResult
(或者有我无法找到它)而且我无法创建MVC框架在void ExecuteResult(ControllerContext context)
上使用异步操作。
那么,我是如何在问题的基础上引用控制器动作的通用实现的?
答案 0 :(得分:1)
你的解决方案似乎过于复杂,非常臭(包含服务位置和其他气味),似乎忽略了ActionResults的意义(命令对象本身,真的)。
实际上,这是The XY Problem的一个很好的例子。而不是询问您的实际问题,即以异步友好的方式重构操作方法中的公共代码,而是提出了一个过于复杂的解决方案,您认为它可以解决您的问题。不幸的是,你无法弄清楚如何使它工作,所以你问这个问题而不是你真正的问题。
您可以使用简单的辅助功能实现您想要的功能。像这样:
public async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command)
{
return await ControllerHelper.Helper(command, ModelState, _mediator,
RedirectToAction(MVC.HomePage.Index()), View(command), View(command));
}
public static class ControllerHelper
{
// You may need to constrain this to where T : class, didn't test it
public static async Task<ActionResult> Helper<T>(T command,
ModelStateDictionary ModelState, IMediator mediator, ActionResult returnAction,
ActionResult successAction, ActionResult failureAction)
{
if (!ModelState.IsValid)
{
return failureResult;
}
var result = await mediator.ProcessCommandAsync(command);
if (!result.IsSuccess())
{
ModelState.AddErrorsToModelState(result);
return successResult;
}
return returnAction;
}
public static void AddErrorsToModelState(this ModelStateDictionary ModelState, ...)
{
// add your errors to the ModelState
}
}
或者,您可以使其成为有状态对象,并通过构造函数注入通过级联依赖项注入介体。不幸的是,注入ModelState并不容易,因此仍然需要作为参数传递给方法。
你也可以只传递ActionResults的字符串,但由于没有RedirectToActionResult对象,你必须搞乱初始化一个RedirectToRoute对象,它更容易传递ActionResult。使用Controllers View()函数比自己构造一个新的ViewResult要容易得多。
您还可以使用Sambo使用的Func<ActionResult>
方法,这使得它进行惰性求值,因此它只在必要时调用RedirectToAction方法。我不认为RedirectToAction有足够的开销使它值得。
答案 1 :(得分:0)
似乎Action仍然是处理逻辑而不是使用ActionResult的最佳位置。
如果代码重复,为什么不使用带有受保护帮助方法的基类......?
public class BaseCommandController : Controller
{
protected IMediator Mediator { get { return DependencyResolver.Current.GetService(typeof (IMediator)) as IMediator; } }
public async virtual Task<ActionResult> BaseDoStuff<TCommand>(TCommand command, Func<ActionResult> success, Func<ActionResult> failure)
{
if (!ModelState.IsValid)
{
return failure();
}
var result = await Mediator.ProcessCommand(command);
if (!result.IsSuccess())
{
AddErrorsToModelState(result);
return failure();
}
return success();
}
private void AddErrorsToModelState(IResponse result)
{
}
}
然后,您的控制器操作将呈现为......
public class DefaultController : BaseCommandController
{
protected async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command)
{
return await BaseDoStuff(command, () => RedirectToAction("Index"), () => View(command));
}
}