在Action方法上使ASP.net MVC 5路由忽略异步后缀

时间:2016-02-24 21:09:34

标签: asp.net-mvc asp.net-web-api asp.net-mvc-5 asp.net-mvc-routing asp.net-web-api2

WebAPI 2智能处理操作方法的Async后缀。例如,如果我创建一个默认的WebAPI项目,它将路由到正确的操作,而不管后缀如何。即:

http://host/api/controller/action      - SUCCEEDS
http://host/api/controller/actionAsync - SUCCEEDS

但是,如果我使用MVC 5创建一个等效的控制器,行为是不同的:

http://host/controller/actionAsync - SUCCEEDS
http://host/controller/action      - FAILS - 404

当Async后缀不存在时,404失败的事实令人惊讶。然而,我试图添加一个路由来处理它,但它仍然失败:

routes.MapRoute(
    name: "DefaultAsync",
    url: "{controller}/{action}Async/{id}",
    defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}
    );

这是我用来测试的MVC和WebAPI控制器(基于具有默认路由的新MVC / WebAPI项目):

public class SampleDto { public string Name; public int Age; }

public class SampleMvcController : Controller
{
    public Task<JsonResult> GetJsonAsync()
    {
        // Illustration Only. This is not a good usage of an Async method,
        // in reality I'm calling out to an Async service.
        return Task.FromResult(
            Json(new SampleDto { Name="Foo", Age = 42}, JsonRequestBehavior.AllowGet));
    }
}

public class SampleWebApiController : ApiController
{
    public Task<SampleDto> GetJsonAsync()
    {
        return Task.FromResult(new SampleDto {Name="Bar", Age=24});
    }
}

由于我正在制作一堆异步方法,所以我不想指定一个动作名称。 routing documentation表明它可以获取可以分隔段的文字,但我还没有运气。

更新 问题是由MVC检索的Action包含Async后缀,但控制器上不存在相应的操作(或操作名称)。与操作MyAction匹配的文章不会将MyActionAsync标识为匹配。

回想起来,这就是为什么路线不起作用的原因。它尝试将操作标识为以Async结尾,但是从匹配中使用的操作中取消Async后缀,这不是我想要做的。如果我只想创建一个MyAction方法(异步但不遵循异步命名约定)并将其映射到相应的MyAction方法,那将非常有用。

2 个答案:

答案 0 :(得分:1)

最初,我非常失望AsyncController类型完全符合我们正在寻找的开箱即用的东西。但显然它是老套的,并且仍然只是“与MVC3的向后兼容性。”

所以我最终做的是制作一个自定义的AsyncControllerActionInvoker并将其分配给自定义控制器的ActionInvoker。 CustomAsyncControllerActionInvoker会覆盖BeginInvokeAction方法,以查看是否存在以“Async”结尾的操作方法以执行相应的操作(例如,您在“Index”中传入,它会查找“IndexAsync”)。如果是,请调用那个,否则,继续按原样继续。

public class HomeController : CustomController
{
    public async Task<ActionResult> IndexAsync()
    {
        ViewBag.Header = "I am a page header."
        var model = new List<int> {1, 2, 3, 4, 5};
        await Task.Run(() => "I'm a task");
        return View(model);
    }
    public ActionResult About()
    {
        return View();
    }
}

public class CustomController : Controller
{
    public CustomController()
    {
        ActionInvoker = new CustomAsyncControllerActionInvoker();
    }
}

public class CustomAsyncControllerActionInvoker : AsyncControllerActionInvoker
{
    public override IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state)
    {
        var asyncAction = FindAction(controllerContext, GetControllerDescriptor(controllerContext), $"{actionName}Async");
        return asyncAction != null
            ? base.BeginInvokeAction(controllerContext, $"{actionName}Async", callback, state)
            : base.BeginInvokeAction(controllerContext, actionName, callback, state);
    }
}

唉,导航到/ Home / Index正确调用IndexAsync()方法并适当地提供Index.cshtml(而不是IndexAsync.cshtml)视图。路由到同步操作(例如“关于”)将照常处理。

答案 1 :(得分:0)

您应该制定约束以确保您的操作名称以Async结尾,而不是尝试将文字与占位符放在一起。

routes.MapRoute(
    name: "DefaultAsync",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "IndexAsync", id = UrlParameter.Optional },
    constraints: new { action = @".*?Async" }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);