使用属性路由时查询字符串不起作用

Posted

技术标签:

【中文标题】使用属性路由时查询字符串不起作用【英文标题】:Query string not working while using attribute routing 【发布时间】:2014-05-03 18:51:05 【问题描述】:

我正在使用 System.Web.Http.RouteAttributeSystem.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 一样单独用作名词。 @Kenta GetProduct 不会出现在实际的 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/cokelocalhost:12345/1/Names?name=Ted&amp;drink=coke 都有效。

【讨论】:

“痛苦叠加” 也为我工作!谢谢! 对我来说看起来不错 - 路由是查询字符串之前的位。你只是说可以是空的 如果 [Route("")] 已经被标记在同一个控制器中的另一个方法上,这将不起作用 我否决了这个答案,因为如果它在某些情况下有效,那么适用于所有情况的最佳答案来自 Tony(见下文)。【参考方案3】:

使用属性路由,您需要指定默认值,以便它们是可选的。

[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 Get(string name, string drink, string sport = null)【参考方案4】:

也只是我的一个旁注。为了使 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&amp;sport=rugby&amp;drink=coke,在 URL 中没有姓名、运动或饮料,因此它不会匹配此属性路由。我们在匹配路由时不考虑查询字符串参数。

要解决这个问题,您需要在属性路由中将所有 3 个选项设为可选。然后它将匹配请求。

【讨论】:

以上是关于使用属性路由时查询字符串不起作用的主要内容,如果未能解决你的问题,请参考以下文章

带有查询参数的 MVC 属性路由不起作用

使用 WebApi [Route] 属性时 FluentValidation 不起作用

为啥我的属性路由不起作用?

.Net Framework 4.6.1 WebApi,属性路由和默认路由不起作用

asp.net webapi 2属性路由不起作用

Sitecore 8.1 中的 Web API 属性路由不起作用