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

ASP.NET Core Route Attributes - ID and hyphenated slug separated by a hyphen

我正在尝试通过 Route 属性为我的控制器操作之一配置以下 url 结构:

/products/12345-purest-green-widgets

这是我目前的路线:

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

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

/products/12345-purest

并且还匹配一个单词后的尾随连字符,一旦我添加任何其他内容它就不匹配了。

有趣的是,如果我将字符串文字连字符(不是正则表达式的连字符)换成 /,它的整体效果很好:

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

匹配成功:

/products/12345/purest-green-widgets

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

如果深入研究,您会发现路由中间件甚至在应用路由约束之前就贪婪地拆分复杂的路由段,如 {id:int}-{name:regex([[\w\-]]+)}。 (在启动时使用路由属性和路由 table 都会发生)

这意味着:

  • 与 url 类似 products/123-foo,路由匹配 123 作为 id 和 foo 作为名称。然后它将应用约束,找到一个匹配项,因为 123 是一个有效的 int 并且 foo 匹配正则表达式。
  • 与 url 类似 products/123-foo-,路由匹配 123 作为 id 和 foo- 作为名称。然后它将应用约束,再次找到匹配项。
  • 与url类似products/123-foo-bar,路由匹配123-foo作为id和bar作为名称。然后它将应用约束,但这次它将失败,因为 123-foo 不是有效的 int!

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

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

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

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

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

    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 而不是使用 VS 中的 ISS):

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()之后在Startup中用services.Configure<RouteOptions>(opts => opts.ConstraintMap.Add("customint", typeof(CustomIntRouteConstraint)))

注册

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

很晚才回答,但如果我们纠正 startup.cs

中的默认映射路由,则可以删除 Id Hypenating

改变

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