使 ASP.net MVC 5 路由忽略 Action 方法上的异步后缀
Make ASP.net MVC 5 Routing Ignore Async Suffix on Action methods
WebAPI 2 智能处理操作方法上的异步后缀。例如,如果我创建一个默认的 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
方法,这将很有用。
与其尝试将文字与占位符放在一起,您应该进行约束以确保您的操作名称以 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 }
);
最初我非常失望,因为 AsyncController 类型完全可以开箱即用地完成我们正在寻找的功能。但显然它已经过时了,只在 "backwards compatibility with 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)视图。同步操作的路由(例如 "About")正常处理。
WebAPI 2 智能处理操作方法上的异步后缀。例如,如果我创建一个默认的 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
方法,这将很有用。
与其尝试将文字与占位符放在一起,您应该进行约束以确保您的操作名称以 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 }
);
最初我非常失望,因为 AsyncController 类型完全可以开箱即用地完成我们正在寻找的功能。但显然它已经过时了,只在 "backwards compatibility with 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)视图。同步操作的路由(例如 "About")正常处理。