【问题标题】:Prevent ASP.NET Web API route engine from deducting custom method names防止 ASP.NET Web API 路由引擎扣除自定义方法名
【发布时间】:2012-12-03 10:08:35
【问题描述】:

我有两个方法的 Web API 控制器 - 假设第一个方法返回纯项目列表,第二个返回分配给特定用户的所有项目。

public class ProjectController: ApiController
{
    public IQueryable<Project> Get() { ... }

    [HttpGet]
    public IQueryable<Project> ForUser(int userId) { ... }
}

在这种情况下方法实现并不重要。

Web API 路由配置也进行了调整以支持自定义方法名称。

config.Routes.MapHttpRoute(
    "DefaultApi",
    "api/v1/{controller}/{id}",
    new { id = RouteParameter.Optional }
);

config.Routes.MapHttpRoute(
    "DefaultApiWithAction",
    "api/v1/{controller}/{action}");

它工作正常,我可以访问/api/v1/projects//api/v1/projects/forUser/ 端点,但似乎路由引擎太聪明了,所以它决定/api/v1/projects?userId=1 请求可能匹配ForUser(..) 方法(由于@ 987654327@ 参数名称,我猜)并忽略路由的{action} 部分。

有什么办法可以避免这种行为并要求在 URL 中明确指定操作部分?

【问题讨论】:

    标签: c# asp.net-mvc asp.net-web-api


    【解决方案1】:

    几件事。首先是这条路线:

    config.Routes.MapHttpRoute(
        "DefaultApiWithAction",
        "api/v1/{controller}/{action}",
        new { id = RouteParameter.Optional });
    

    没有“action”作为可选参数。您已将 id 包含为可选(我认为是错字),但由于它在路线中不存在,因此您不会仅与一个补充段匹配。只有包含两个部分(控制器和操作)的 URL 将通过此路由。这个网址:

    /api/v1/projects?userId=1
    

    ...包含单个段,不会。此路由以及任何其他缺少第二个组件的路由将默认使用此路由:

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

    ...它只需要一个控制器和一个可选的 ID。您需要重新格式化给定的 URL 以获取操作参数,或者重写您的路由以使操作可选并根据需要设置默认值。这一切都取决于您的应用程序架构,但总是在简单方面犯错。路线可能会变得非常复杂——通常越简单越好。

    至于必需/可选的路由组件,请记住以下两点:

    • 所有路由段都是必需的,除非它们在匿名对象中设置为可选。
    • 如果段具有默认值,也可以排除它们,通过在匿名对象中以placeholder = value 的形式提供一个来设置。

    【讨论】:

    • 不幸的是,您似乎弄错了,单段/api/v1/projects?userId URL 被路由到ForUser() 操作。我猜这是因为ForUser() 动作有userId 参数,它与URL 中的userId 参数相匹配。 (我还编辑了问题并删除了误导性部分)
    • @AndrewKhmylov - 我想也许我解释得不够好。单段 URL 确实 确实匹配 ForUser 操作,但它这样做是因为它通过 api/v1/{controller}/{id} 而不是 api/v1/{controller}/{action}。这是因为 URL 中没有指定操作。然后它会尽其所能,最有可能通过查看 ID 来尝试找到它认为的最佳匹配。如果您希望它进入特定匹配项,您需要更改路线以添加默认操作,或者将操作添加到您的 url,因为它目前不包含一个。
    【解决方案2】:

    我不完全理解你的问题。 /api/v1/projects?userId=1 不应该调用 ForUser 操作吗?

    无论如何,要执行所需的操作,请将您的 HttpRoute 设置为:

       name: "DefaultApi",
       routeTemplate: "api/v1/{controller}/{action}/{id}",
       defaults: new { id = System.Web.Http.RouteParameter.Optional });
    

    现在您可以这样调用: /api/v1/projects/ForUser/2

    【讨论】:

    • 目的是使操作部分显式,以便 URL 有意义。现在,我可以将任何内容替换为操作名称,它仍将使用 ForUser 操作。至于添加“id”参数 - 我同意它可以工作,但这并不是我一直在寻找的(因为某些操作可能有多个参数)。还是谢谢。
    • /api/v1/projects/ForUser/2 的解释与/api/v1/projects/ForUser?userId=2 相同。要添加更多参数,只需执行/api/v1/projects/ForUser?userId=2&amp;userName=andrew
    【解决方案3】:

    我终于想出了满足我要求的解决方案。我将this answerlevibuser1797792 建议的想法结合到以下配置中:

    config.Routes.MapHttpRoute(
        "DefaultApiWithActionAndOptionalId",
        "api/v1/{controller}/{action}/{id}",
        new {id = RouteParameter.Optional});
    
    config.Routes.MapHttpRoute(
        "DefaultApiGet",
        "api/v1/{controller}",
        new { action = "Get" },
        new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
    

    请注意,这里的配置顺序很重要。

    首先,带有 any 查询字符串的/api/v1/projects 请求(即使参数名称与其他操作的参数匹配)通过第二条路由分派到Get() 方法。这很重要,因为在实际项目中,我有一个自定义操作过滤器附加到此操作,它根据提供的请求参数过滤返回的IQueryable

    api/v1/projects/forUser/1-like 请求通过第一条路由分派到ForUser(int id) 方法。将 userId 参数重命名为 id 允许构造更清晰的 URL。

    显然,这种方法有一些局限性,但在我的具体情况下,这就是我所需要的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-23
      • 1970-01-01
      • 2015-11-10
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 2012-03-23
      • 1970-01-01
      相关资源
      最近更新 更多