使用属性路由时查询字符串不起作用
Posted
技术标签:
【中文标题】使用属性路由时查询字符串不起作用【英文标题】:Query string not working while using attribute routing 【发布时间】:2014-05-03 18:51:05 【问题描述】:我正在使用 System.Web.Http.RouteAttribute
和 System.Web.Http.RoutePrefixAttribute
为我的 Web API 2 应用程序启用更清晰的 URL。对于我的大多数请求,我可以使用路由(例如Controller/param1/param2
)或者我可以使用查询字符串(例如Controller?param1=bob&param2=mary
)。
不幸的是,我的一个控制器(并且只有一个)失败了。这是我的控制器:
[RoutePrefix("1/Names")]
public class NamesController : ApiController
[HttpGet]
[Route("name/sport/drink")]
public List<int> Get(string name, string sport, string drink)
// Code removed...
[HttpGet]
[Route("name/drink")]
public List<int> Get(string name, string drink)
// Code removed...
当我使用路由向其中一个发出请求时,两者都可以正常工作。但是,如果我使用查询字符串,它会失败,告诉我该路径不存在。
我尝试将以下内容添加到我的 WebApiConfig.cs
类的 Register(HttpConfiguration config)
函数中(在默认路由之前和之后),但它什么也没做:
config.Routes.MapHttpRoute(
name: "NameRoute",
routeTemplate: "verId/Names/name/sport/drink",
defaults: new name = RouteParameter.Optional, sport = RouteParameter.Optional, drink = RouteParameter.Optional ,
constraints: new verId = @"\d+" );
所以为了清楚起见,我希望能够同时做到这一点:
localhost:12345/1/Names/Ted/rugby/coke
localhost:12345/1/Names/Ted/coke
和,
localhost:12345/1/Names?name=Ted&sport=rugby&drink=coke
localhost:12345/1/Names?name=Ted&drink=coke
但遗憾的是查询字符串版本不起作用! :(
更新
我已经完全删除了第二个动作,现在尝试只使用带有可选参数的单个动作。正如 Tony 建议的那样,我已将我的路线属性更改为 [Route("name/drink/sport?")]
以使运动可以为空,但这现在会阻止 localhost:12345/1/Names/Ted/coke
出于某种原因成为有效路线。查询字符串的行为方式与以前相同。
更新 2 我现在在我的控制器中有一个单一的动作:
[RoutePrefix("1/Names")]
public class NamesController : ApiController
[HttpGet]
[Route("name/drink/sport?")]
public List<int> Get(string name, string drink, string sport = "")
// Code removed...
但是,使用查询字符串仍然找不到合适的路径,而使用路由方法却可以。
【问题讨论】:
这将为您节省数小时的挫败感。 nuget.org/packages/routedebugger/2.1.4 很好,我也要检查一下 感谢@IanP 的指点。不幸的是,如果它找不到路径,它就无法告诉您它正在使用什么路线。我不知道的有用工具!我认为 Glimpse 做了类似的事情。 【参考方案1】:在尝试为当前项目构建 Web api 时,我遇到了同样的问题“如何将搜索参数包含为查询字符串?”。谷歌搜索后,以下对我来说很好:
API 控制器操作:
[HttpGet, Route("search/categoryid=categoryid/ordercode=ordercode")]
public Task<IHttpActionResult> GetProducts(string categoryId, string orderCode)
我通过邮递员尝试的网址:
http://localhost/PD/search?categoryid=all-products&ordercode=star-1932
http://localhost/PD is my hosted api
【讨论】:
GetPproduct 不得用作根据 REST 标准的 API 端点。它应该像 Products 一样单独用作名词。 @KentaGetProduct
不会出现在实际的 URI 中,看到 [Route()]
属性意味着 URI 是 /PD/search?
是可以接受的。不要忘记没有“REST 标准”或规范,并且有余地。【参考方案2】:
经过艰苦的摆弄和谷歌搜索,我想出了一个“修复”。我不知道这是否是理想的/最佳实践/明显的老错误,但它解决了我的问题。
除了我已经使用的路由属性之外,我所做的只是添加[Route("")]
。这基本上允许 Web API 2 路由允许查询字符串,因为这现在是一个有效的路由。
现在是一个例子:
[HttpGet]
[Route("")]
[Route("name/drink/sport?")]
public List<int> Get(string name, string drink, string sport = "")
// Code removed...
这使得localhost:12345/1/Names/Ted/coke
和localhost:12345/1/Names?name=Ted&drink=coke
都有效。
【讨论】:
“痛苦叠加”使用属性路由,您需要指定默认值,以便它们是可选的。
[Route("name/sport=Football/drink=Coke")]
分配一个值将允许它是可选的,因此您不必包含它,它会将值传递给指定。
我没有为此测试过查询字符串,但它应该可以正常工作。
我刚刚重新阅读了这个问题,我发现你有 2 个 Get 动词具有相同的路径,我相信这会导致冲突,因为路由不知道使用哪一个,也许使用可选参数会有所帮助。您还可以指定一个可以为 null 并检查方法以了解如何继续。
[Route("name/sport?/drink?")]
然后检查方法中的变量是否为null,根据需要处理。
希望这会有所帮助吗?哈哈
如果不是这个网站可能会,它有更多关于属性路由的细节。
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
来自该网站的剪辑:
可选参数和默认值你可以指定一个 参数是可选的,通过在参数中添加问号,即 是:
[Route("countries/name?")] public Country GetCountry(string name = "USA")
目前,必须在可选参数上指定默认值 为了行动选择成功,但我们可以调查解除 限制。 (如果这很重要,请告诉我们。)
默认值可以用类似的方式指定:
[Route("countries/name=USA")] public Country GetCountry(string name)
可选参数'?'并且默认值必须出现在 参数定义中的内联约束。
【讨论】:
谢谢托尼。我已根据您的建议更新了我的问题。我已经删除了其中一个函数,所以现在我只有一个函数可以排序。不过问题还是出现了! :( 看看你拥有的东西,据我所知,它应该是有效的。不幸的是,我不在一个可以进一步测试和帮助你的地方。伊恩评论的那个路由调试包也许能提供进一步的帮助。 我唯一的另一个想法是尝试做 sport=None 看看你是否至少可以进入这个方法,你至少会知道它进入了那里,你可以做一个检查是否没有。它会让你滚动,直到我假设一个更好的答案出现。 不幸的是,我仍然卡住了。如果我删除路由属性,我可以很好地使用查询字符串,但不能使用路由参数!如果我能两者都做就好了…… 我不认为这直接相关,因为它主要谈论 webapi,但他们说这种方式不支持可选参数。 connect.microsoft.com/VisualStudio/feedback/details/451296 但是,我确实找到了另一个引用可选查询字符串参数的链接,他们在方法签名中使用“字符串 myParam = null”而不是空字符串。尝试切换它,看看它是否有帮助。即: public List也只是我的一个旁注。为了使 queryString 参数起作用,您需要为您的方法参数提供一个默认值以使其成为可选。就像您在正常调用 C# 方法时也会做的那样。
[RoutePrefix("api/v1/profile")]
public class ProfileController : ApiController
...
[HttpGet]
[Route("profileUid")]
public IHttpActionResult GetProfile(string profileUid, long? someOtherId)
// ...
...
这允许我像这样调用端点:
/api/v1/profile/someUid
/api/v1/profile/someUid?someOtherId=123
【讨论】:
目前为止最简单的方法。谢谢!【参考方案5】:使用Route("search/categoryid=categoryid/ordercode=ordercode")
将使您能够使用mosharaf hossain 回答的查询字符串和内联路由参数。写下这个答案,因为这应该是最佳答案和最佳方法。如果您有多个 Gets/Puts/Posts/Delete,使用 Route("")
会导致问题。
【讨论】:
【参考方案6】:我使用 FromUri 属性作为解决方案
[Route("UsageAggregationDaily")]
public async Task<HttpResponseMessage> UsageAggregationDaily([FromUri] string userId = null, [FromUri] DateTime? startDate = null, [FromUri] DateTime? endDate = null)
【讨论】:
【参考方案7】:这与@bhargav kishore mummadireddy's 的答案略有不同,但有一个重要的偏差。他的回答会将查询字符串值默认为实际的非空值。这个答案会将它们默认为空。
它允许您通过路径路由或使用查询字符串来调用控制器。本质上,它将查询字符串的默认值设置为空,这意味着它将始终被路由。
这对我很重要,因为如果未指定查询字符串,我想返回 400(错误请求),而不是让 ASP.NET 返回“无法在此控制器上找到此方法”错误。
[RoutePrefix("api/AppUsageReporting")]
public class AppUsageReportingController : ApiController
[HttpGet]
// Specify default routing parameters if the parameters aren't specified
[Route("UsageAggregationDaily/userId=/startDate=/endDate=")]
public async Task<HttpResponseMessage> UsageAggregationDaily(string userId, DateTime? startDate, DateTime? endDate)
if (String.IsNullOrEmpty(userId))
return Request.CreateResponse(HttpStatusCode.BadRequest, $"nameof(userId) was not specified.");
if (!startDate.HasValue)
return Request.CreateResponse(HttpStatusCode.BadRequest, $"nameof(startDate) was not specified.");
if (!endDate.HasValue)
return Request.CreateResponse(HttpStatusCode.BadRequest, $"nameof(endDate) was not specified.");
【讨论】:
【参考方案8】:因为你有[Route("name/drink/sport?")]
作为属性路由,所以这个代码永远不会被命中。
config.Routes.MapHttpRoute(
name: "NameRoute",
routeTemplate: "verId/Names/name/sport/drink",
defaults: new name = RouteParameter.Optional, sport = RouteParameter.Optional, drink = RouteParameter.Optional ,
constraints: new verId = @"\d+" );
所以这里只有属性路由[Route("name/drink/sport?")]
会受到尊重。由于您的请求 localhost:12345/1/Names?name=Ted&sport=rugby&drink=coke
,在 URL 中没有姓名、运动或饮料,因此它不会匹配此属性路由。我们在匹配路由时不考虑查询字符串参数。
要解决这个问题,您需要在属性路由中将所有 3 个选项设为可选。然后它将匹配请求。
【讨论】:
以上是关于使用属性路由时查询字符串不起作用的主要内容,如果未能解决你的问题,请参考以下文章
使用 WebApi [Route] 属性时 FluentValidation 不起作用