防止 ASP.NET Web API 路由引擎扣除自定义方法名

Posted

技术标签:

【中文标题】防止 ASP.NET Web API 路由引擎扣除自定义方法名【英文标题】:Prevent ASP.NET Web API route engine from deducting custom method names 【发布时间】: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 中明确指定操作部分?

【问题讨论】:

【参考方案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 answer 和 levibuser1797792 建议的想法结合到以下配置中:

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。

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

【讨论】:

以上是关于防止 ASP.NET Web API 路由引擎扣除自定义方法名的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Web API 2第五课——Web API路由

防止 FormsAuthenticationModule 拦截 ASP.NET Web API 响应

Asp.Net Web API 2第六课——Web API路由和动作选择

Asp.Net Web APi 路由的特点

Web API系列教程2.2 — ASP.NET Web API中的路由和动作选择机制

ASP.NET Web API路由系统:路由系统的几个核心类型