MVC5 Async ActionResult。那可能吗?

时间:2014-12-03 12:12:23

标签: c# asp.net-mvc asynchronous cqrs

在我们的申请中,我们有CQRS:我们IAsyncCommandIAsyncCommandHandler<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)上使用异步操作。

那么,我是如何在问题的基础上引用控制器动作的通用实现的?

2 个答案:

答案 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));
    }
}