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

mat*_*wrk 3 asp.net asp.net-mvc routes asp.net-mvc-routing asp.net-core

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

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

这是我的路线:

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

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

/产品/ 12345,最纯净

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

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

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

成功匹配:

/产品/ 12345 /最纯净的绿色的小部件

所以它似乎绊倒了字符串文字连字符.有任何想法吗?

Dan*_*.G. 5

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

这意味着:

  • 使用url products/123-foo,路由匹配123为id和fooname.然后它将应用约束,找到匹配作为123有效的int并foo匹配正则表达式.
  • 使用url products/123-foo-,路由匹配123为id和foo-name.然后它将应用约束,再次找到匹配.
  • 使用url products/123-foo-bar,路由匹配123-foo为id和barname.然后它将应用约束,但这次它将失败,因为123-foo它不是有效的int!

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

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

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

问题是您需要从该单个参数手动提取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)
    {
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 您可以使用其模型绑定程序提供程序和参数的某些注释属性创建新的模型绑定程序.这可能是最干净的,因为它类似于上面的方法,但是在模型绑定的预期方式上扩展了MVC,但是我没有时间去探索它:

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

为了调试路由约束,您可以将日志记录设置为调试,您应该在控制台中看到这样的消息(您可能需要从控制台运行应用程序,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'.
Run Code Online (Sandbox Code Playgroud)

您也可以手动复制int route约束,然后在启动后将services.AddMvc()其注册到services.Configure<RouteOptions>(opts => opts.ConstraintMap.Add("customint", typeof(CustomIntRouteConstraint)))

本博客中描述的方法也可能有助于调试.