Web API路由 - api/{controller}/{action}/{id} "出现故障" api/{controller}/{id}

96

我在Global.asax中有默认的路由:

 RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

我想要针对一个特定的函数进行操作,因此我创建了另一条路由:

RouteTable.Routes.MapHttpRoute(
         name: "WithActionApi",
         routeTemplate: "api/{controller}/{action}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

所以,在我的控制器中,我有:

    public string Get(int id)
    {
        return "object of id id";
    }        

    [HttpGet]
    public IEnumerable<string> ByCategoryId(int id)
    {
        return new string[] { "byCategory1", "byCategory2" };
    }

调用.../api/records/bycategoryid/5将会给我想要的东西。 然而,调用.../api/records/1将会给我以下错误:

找到多个匹配该请求的操作:...

我明白这是为什么 - 路由只是定义哪些URL有效,但当涉及到函数匹配时,Get(int id)ByCategoryId(int id) 都匹配api/{controller}/{id} ,这就是困扰框架的原因。

我需要做什么才能让默认的API路由再次工作,并保留带有{action}的那个路由?我想过创建一个名为RecordByCategoryIdController的不同控制器以匹配默认的API路由,并请求.../api/recordbycategoryid/5。但我认为这是一种"不好的"(因此不令人满意的)解决方案。我已经寻找了这方面的答案,但没有任何教程在使用具有{action}的路由时提到过这个问题。

5个回答

106

路由引擎使用添加规则的顺序。一旦它找到第一个匹配的规则,它就会停止检查其他规则并使用该规则搜索控制器和操作。

因此,你应该:

  1. 将具体的规则放在通用规则(例如默认规则)之前,这意味着应首先使用 RouteTable.Routes.MapHttpRoute 映射 "WithActionApi" ,然后是 "DefaultApi"。

  2. 删除 "WithActionApi" 规则中的 defaults: new { id = System.Web.Http.RouteParameter.Optional } 参数,因为一旦 id 是可选的,像 "/api/{part1}/{part2}" 这样的 URL 将永远不会进入 "DefaultApi"。

  3. 向 "DefaultApi" 添加一个命名操作以告诉路由引擎要进入哪个操作。否则,一旦控制器中有多个操作,引擎就不知道使用哪一个并抛出 "Multiple actions were found that match the request: ..." 异常。然后,使用 ActionNameAttribute 使其与 Get 方法匹配。

因此,你的路由应该像这样:

// Map this rule first
RouteTable.Routes.MapRoute(
     "WithActionApi",
     "api/{controller}/{action}/{id}"
 );

RouteTable.Routes.MapRoute(
    "DefaultApi",
    "api/{controller}/{id}",
    new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);

你的控制器:

[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
    return "object of id id";
}        

[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
    return new string[] { "byCategory1", "byCategory2" };
}

1
我尝试了上面的建议,一切都如预期般工作。非常感谢你的“秘诀”。 - Mickael Caruso
在你的回答中第二点,如果 id 是可选的,那么像 /api/{part1}/{part2} 这样的 URL,如果无法找到匹配的操作,则仍会进入 DefaultApi 路由。如果我说错了,请纠正我。 - orad
如果我只想要api/{controller}/{action},而不需要id,会发生什么情况?如果其他人也遇到了同样的问题,请忘记WebApiConfig,阅读有关属性路由的内容。 - ahsant

41
你可以通过属性路由来解决你的问题。 控制器
[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }

jQuery中的URI

api/category/1

路由配置

using System.Web.Http;

namespace WebApplication
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();

            // Other Web API configuration not shown.
        }
    }
}

并且您的默认路由按照默认的约定式路由工作。

控制器

public string Get(int id)
    {
        return "object of id id";
    }   

Jquery中的URI

/api/records/1 

路由配置

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

        // Convention-based routing.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

查看更多信息,请参考此处的属性路由和约定路由&此处


好的回答。但是我们不能将 "api/{controller}/{id}" 添加到所有内容中吗? - karim
属性路由绝对解决了这个问题。一个重要的点:在Web API 2之前,Web API项目模板生成的代码如下: protected void Application_Start() { WebApiConfig.Register(GlobalConfiguration.Configuration); }如果启用属性路由,此代码将抛出异常。如果您升级现有的Web API项目以使用属性路由,请确保将此配置代码更新为以下内容:protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); } - Deeb

0

试一下这个。

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        var json = config.Formatters.JsonFormatter;
        json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional , Action =RouteParameter.Optional }

        );
    }
}

0
可能的原因也可能是您没有从ApiController继承Controller。我也遇到了这个问题,花了一段时间才理解。

0
为了区分路由,请尝试添加一个约束条件,即id必须是数字:
RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         constraints: new { id = @"\d+" }, // Only matches if "id" is one or more digits.
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接