ASP.NET核心路由属性 - 由连字符分隔的ID和带连字符的slug

时间:2016-10-12 23:26:37

标签: asp.net asp.net-mvc routes asp.net-core asp.net-mvc-routing

我尝试通过Route属性为我的某个控制器操作配置以下网址结构:

  

/产品/ 12345-最纯的 - 绿 - 小部件

这是我现在的路线:

[Route(@"/products/{id:int}-{slug:regex([[\w\-]]+)}")]
public ContentResult Show(int id, string slug)

这与预期的路线不匹配,但确实匹配:

  

/产品/ 12345-最纯

并且在一个单词之后也匹配一个尾随连字符,只要我添加其他任何不匹配的内容。

有趣的是,如果我为/换掉字符串文字连字符(而不是正则表达式连字符),它的工作原理很好:

[Route(@"/products/{id:int}/{slug:regex([[\w\-]]+)}")]
public ContentResult Show(int id, string slug)

成功匹配:

  

/产品/ 12345 /最纯净 - 绿 - 小部件

所以它似乎绊倒了字符串文字连字符。有什么想法吗?

2 个答案:

答案 0 :(得分:5)

如果你深入挖掘,你会发现即使在应用路由约束之前,路由中间件也会贪婪地拆分复杂的路由段,如{id:int}-{name:regex([[\w\-]]+)}。 (在启动时使用路由属性和路由表发生)

这意味着:

  • 使用products/123-foo之类的网址,路线将123与ID匹配,foo作为名称。然后它将应用约束,找到匹配,因为123是有效的int,foo匹配正则表达式。
  • 使用products/123-foo-之类的网址,路线将123与ID匹配,foo-作为名称。然后它将应用约束,再次找到匹配。
  • 使用products/123-foo-bar之类的网址,路线将123-foo与ID匹配,bar作为名称。然后它将应用约束,但这次它将失败,因为123-foo不是有效的int!

如果您将参数拆分为{id:int}/{name:regex([[\w\-]]+)}中的不同路径段,则不会出现此问题,因为/会按照您的预期将参数拆分为正确。

如果您的路线确实需要具有该形状,那么我将在路线约束中使用单个参数。此参数将包装id和名称:

[Route(@"/products/{combined:regex(^[[\d]]+-[[\w\-]]+$)}")]

问题是您需要手动从该单个参数中提取id和名称。

  • 您可以在控制器操作中手动执行此操作。对于一次性,这可能是可以接受的
  • 您可以创建一个ActionFilter,并在执行操作之前将组合路由参数拆分为操作参数(覆盖OnActionExecuting)。这仍然非常hacky,特别是我的快速和肮脏的版本:

    public class SplitProductParametersActionFilter : ActionFilterAttribute
    {
        private static Regex combinedRegex = new Regex(@"^([\d]+)-([\w\-]+)$");
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var combined = context.RouteData.Values["combined"].ToString();
            var match = combinedRegex.Match(combined);
            if (match.Success)
            {
                context.ActionArguments.Add("id", int.Parse(match.Groups[1].Value));
                context.ActionArguments.Add("name", match.Groups[2].Value);
            }
        }
    }
    
    [Route(@"/products/{combined:regex(^[[\d]]+-[[\w\-]]+$)}")]
    [SplitProductParametersActionFilter]
    public IActionResult Contact(int id, string name)
    {
    }
    
  • 您可以使用模型绑定提供程序和参数的某些注释属性创建新的模型绑定器。这可能是最干净的,因为它类似于上面的方法,但是以模型绑定的预期方式扩展了MVC,但是我没有时间去探索它:

    [Route(@"/products/{combined:regex(^[[\d]]+-[[\w\-]]+$)}")]
    public IActionResult Contact([FromUrlProduct("combined")]int id, [FromUrlProduct("combined")]string name)
    {
    }
    

为了调试路由约束,您可以将日志记录设置为调试,您应该在控制台中看到这样的消息(您可能需要使用dotnet run从控制台运行应用程序而不是使用ISS来自VS):

dbug: Microsoft.AspNetCore.Routing.RouteConstraintMatcher[1]
      => RequestId:0HKVJG96H1RQE RequestPath:/products/1-foo-bar
      Route value '1-foo' with key 'id' did not match the constraint 'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint'.

您还可以手动复制int route constraint并在services.AddMvc()之后使用services.Configure<RouteOptions>(opts => opts.ConstraintMap.Add("customint", typeof(CustomIntRouteConstraint)))

在初创公司中注册

类似于in this blog描述的方法也可能有助于调试。

答案 1 :(得分:0)

回答得很晚,但是如果我们在startup.cs中更正默认的maproute,则ID消除可以被删除

更改

routes.MapRoute(
                    name: "default",
                   template: "{controller=Home}/{action=Index}/{id?}");
        )

到以下

routes.MapRoute(
                        name: "default",
                       template: "{controller=Home}/{action=Index}");
            )

此后创建的任何路由都将不是Controller / Action / 4,而是Controller / Action?Id = 5