同时使用属性路由和基于约定的路由与Web API和MVC

本文关键字:路由 约定 MVC API Web 于约定 属性 | 更新日期: 2023-09-27 17:50:58

我有一个使用基于约定路由的Asp.net MVC web应用程序。我最近添加了一些Web Api 2控制器,为此我使用了属性路由。尽管文档声称你可以使用这两种方法,但我可以让(属性路由)API方法工作,或者(约定路由)web应用程序方法。

这是RouteConfig.RegisterRoutes():

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        //routes.MapMvcAttributeRoutes();
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Tables", action = "Index", id = UrlParameter.Optional },
            namespaces: new string[] { "Foo.Cms.Controllers" }
        );
    }
这是WebApiConfig.Register():
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
        // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
        // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
        //config.EnableQuerySupport();

        // The models currently only serialize succesfully to xml, so we'll remove the json formatter.
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
    }
这是Application_Start():
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        GlobalConfiguration.Configure(WebApiConfig.Register);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
        GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
    }

这样,只有到web api控制器的路由有效。如果我切换GlobalConfiguration.Register()和RouteConfig.RegisterRoutes(),如下所示:

        RouteConfig.RegisterRoutes(RouteTable.Routes);
        GlobalConfiguration.Configure(WebApiConfig.Register);

…只有基于约定的路由才能工作。

我很茫然。这是怎么回事?

编辑:

我想达到的目标:

应用程序当前使用基本的{controller}/{action}/parameters约定。因此,我有一个名为ElementsController的控制器,例如,它有一个Index()方法路由到/Elements或一个ListPublic()方法路由到/Elements/ListPublic。我通过上面提到的基于约定的路由实现了这一点。

我也有一堆Web Api控制器(例如,TablesController),我想路由到使用/Api/v0/tables路由。我试着这样做:

[RoutePrefix("api/v0/tables")]
public class TablesController : ApiController
{
    [Route()]
    public string Get()
    {
        // ...
    }
}

正如你所看到的,它不是相同的路由模式:api调用都以api/v0/为前缀。然而,由于某些原因,它似乎仍然将它们视为默认的{controller}/{action}路由。

同时使用属性路由和基于约定的路由与Web API和MVC

实际情况是"第一个注册的"路由正在生效。如果我有一个MVC路由定义为{controller}/{action}/{id}

和定义为

的Web API路由

{controller}/{action}/{id}

第一个注册的路由生效。

为什么会这样?假设您向服务器发送一个请求

foo/bar/1

这与哪条路由匹配?

两个!

它将选择第一个与路由匹配的结果,而不管使用的路由类型。如果您想要一些关于如何使这些路由工作的示例,请查看这个链接

如果一个URL匹配两个不同的路由模板,不管它们是引用MVC还是Web API,唯一的规则是第一个注册的路由将被用来选择要执行的操作。

在您的特殊情况下,如果您向此URL: /api/v0/tables发出GET请求,则操作选择器开始检查已注册的路由。

你注册的第一个路由是这样的MVC路由:/{controller}/{action}/{id}。因此,模板匹配以下值:

controller = "api"
action = "v0"
id = "tables"

如果你在MVC路由之前注册属性路由,第一个匹配的路由模板将是你的路由属性模板,因此Web API控制器动作将被正确选择。

换句话说,如果你需要在同一个应用程序中路由Web API和MVC,你必须为每个操作使用匹配不同url的路由,并且要注意它们注册的顺序:先注册更具体的模板,然后是更通用的模板,这样通用模板就不会吞噬应该与特定模板匹配的uri。

这不是唯一的选择。你也可以使用路由约束,例如,如果你所有的API都有一个特定的命名约定,可以用一个约束来检查(例如一个Regex约束),你仍然可以使用约定路由的Web API。

注意:除了路由匹配之外,操作还需要支持HTTP方法(如POST, GET等)。所以你可以在同一个路由中有两个接受不同方法的类似动作,而动作选择器会知道选择哪一个,但这并不能解决你的问题