【问题标题】:ASP.NET MVC: Route with optional parameter, but if supplied, must match \d+ASP.NET MVC:带有可选参数的路由,但如果提供,必须匹配 \d+
【发布时间】:2010-10-05 09:20:18
【问题描述】:

我正在尝试编写一个包含可为空的 int 的路由。应该可以同时访问/profile//profile/\d+

routes.MapRoute("ProfileDetails", "profile/{userId}",
                new {controller = "Profile",
                     action = "Details",
                     userId = UrlParameter.Optional},
                new {userId = @"\d+"});

如您所见,我说userId 是可选的,但它应该匹配正则表达式\d+。这不起作用,我明白为什么了。

但是我如何构造一个既匹配/profile/ 又匹配/profile/ 后跟数字的路由?

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-2


    【解决方案1】:

    最简单的方法是添加另一个没有userId参数的路由,所以你有一个后备:

    routes.MapRoute("ProfileDetails", "profile/{userId}",
                    new {controller = "Profile",
                         action = "Details",
                         userId = UrlParameter.Optional},
                    new {userId = @"\d+"});
    
    routes.MapRoute("Profile", "profile",
                    new {controller = "Profile",
                         action = "Details"});
    

    据我所知,您可以做到这一点的唯一其他方法是使用自定义约束。所以你的路线会变成:

    routes.MapRoute("ProfileDetails", "profile/{userId}",
                    new {controller = "Profile",
                         action = "Details",
                         userId = UrlParameter.Optional},
                    new {userId = new NullableConstraint());
    

    自定义约束代码如下所示:

    using System;
    using System.Web;
    using System.Web.Routing;
    using System.Web.Mvc;
    
    namespace YourNamespace
    {
        public class NullableConstraint : IRouteConstraint
        {
            public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
            {
                if (routeDirection == RouteDirection.IncomingRequest && parameterName == "userId")
                {
                    // If the userId param is empty (weird way of checking, I know)
                    if (values["userId"] == UrlParameter.Optional)
                        return true;
    
                    // If the userId param is an int
                    int id;
                    if (Int32.TryParse(values["userId"].ToString(), out id))
                        return true;
                }
    
                return false;
            }
        }
    }
    

    我不知道 NullableConstraint 是这里最好的名字,但这取决于你!

    【讨论】:

    • 我做了一些与此类似的东西,但更通用一些,构造函数采用正则表达式来匹配提供的值。谢谢!
    • 我知道我来晚了,但最后一行不应该返回 true 吗?否则,如果路由方向是 UrlGeneration,则约束总是失败。
    • 如果最后一行返回 true 那么 anything 将匹配约束...但是是的,你是对的,这不考虑传入请求以外的任何内容路由。现在看,我原以为我们可以完全删除 if 语句的 routeDirection 部分;明天我会测试并编辑如果是这种情况。
    • 也许路由方向代码的假设是我们相信该参数对于 URL 生成是有效的(因为开发人员提供了该值)。
    • 使用int? id 将允许可以为空的int,检查here,希望有所帮助。
    【解决方案2】:

    自从回答了这个问题后,可能发生了一些变化,但我能够改变这个:

    routes.MapPageRoute(
        null,
        "projects/{operation}/{id}",
        "~/Projects/ProjectWizard.aspx",
        true,
        new RouteValueDictionary(new
        {
            operation = "new",
            id = UrlParameter.Optional
        }),
        new RouteValueDictionary(new
        {
            id = new NullableExpressionConstraint(@"\d+")
        })
    );
    

    有了这个:

    routes.MapPageRoute(
        null,
        "projects/{operation}/{id}",
        "~/Projects/ProjectWizard.aspx",
        true,
        new RouteValueDictionary(new
        {
            operation = "new",
            id = UrlParameter.Optional
        }),
        new RouteValueDictionary(new
        {
            id = @"\d*"
        })
    );
    

    只需在正则表达式中使用* 而不是+ 即可完成相同的任务。如果不包含参数,路由仍然会触发,但如果包含,它只会在值是有效整数时触发。否则会失败。

    【讨论】:

      【解决方案3】:

      ASP.NET MVC 3 已经解决了这个问题,作为Alex Ford brought out,您可以使用\d* 而不是编写自定义约束。如果您的模式更复杂,例如使用\d{4} 查找年份,只需确保您的模式与您想要的以及空字符串(例如(\d{4})?\d{4}|^$)匹配。任何让你开心的事情。

      如果您仍在使用 ASP.NET MVC 2 并希望使用 Mark Bell's exampleNYCChris' example,请注意只要 URL 参数 包含 匹配到你的模式。这意味着模式\d+ 将匹配abc123def 之类的参数。为避免这种情况,请在定义路由时或在自定义约束中使用 ^()$ 包装模式。 (如果您查看Reflector 中的System.Web.Routing.Route.ProcessConstraint,您会发现它在使用内置约束时为您执行此操作。它还设置了CultureInvariant, Compiled, and IgnoreCase 选项。)

      由于我已经用上面提到的默认行为编写了自己的自定义约束,然后才意识到我不必使用它,我将把它留在这里:

      public class OptionalConstraint : IRouteConstraint
      {
        public OptionalConstraint(Regex regex)
        {
          this.Regex = regex;
        }
      
        public OptionalConstraint(string pattern) :
          this(new Regex("^(" + pattern + ")$",
            RegexOptions.CultureInvariant |
            RegexOptions.Compiled |
            RegexOptions.IgnoreCase)) { }
      
        public Regex Regex { get; set; }
      
        public bool Match(HttpContextBase httpContext,
                          Route route,
                          string parameterName,
                          RouteValueDictionary values,
                          RouteDirection routeDirection)
        {
          if(routeDirection == RouteDirection.IncomingRequest)
          {
            object value = values[parameterName];
            if(value == UrlParameter.Optional)
              return true;
            if(this.Regex.IsMatch(value.ToString()))
              return true;
          }
      
          return false;
        }
      }
      

      这是一个示例路线:

      routes.MapRoute("PostsByDate",
                      "{year}/{month}",
                      new { controller = "Posts",
                            action = "ByDate",
                            month = UrlParameter.Optional },
                      new { year = @"\d{4}",
                            month = new OptionalConstraint(@"\d\d") });
      

      【讨论】:

      • ASP.NET MVC 4 还优雅地允许正则表达式匹配(\d{4})? 等空字符串,这让我很高兴。
      • 也许是时候将这个答案标记为正确答案了?
      【解决方案4】:

      你的正则表达式应该是 \d* 吗?

      【讨论】:

      • \d*完全不匹配,不管有没有UrlParameter.Optional
      • @DenizDogan 不确定从那以后是否发生了变化,但我刚刚测试了\d*,它完成了与OptionalRegExConstraint完全相同的事情。
      【解决方案5】:

      感谢 Mark Bell 的回答,对我帮助很大。

      我想知道为什么您在约束中对“userId”的检查进行硬编码?我稍微改写了你的类,比如使用parameterName 参数,它似乎工作得很好。

      我这样做有什么遗漏吗?

      public class OptionalRegExConstraint : IRouteConstraint
      {
          private readonly Regex _regEx;
      
          public OptionalRegExConstraint(string matchExpression=null)
          {
              if (!string.IsNullOrEmpty(matchExpression))
                  _regEx = new Regex(matchExpression);
          }
      
          public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
          {
              if (routeDirection == RouteDirection.IncomingRequest)
              {
                  if (values[parameterName] == UrlParameter.Optional) return true;
      
                  return _regEx != null && _regEx.Match(values[parameterName].ToString()).Success;
              }
              return false;
          }
      }
      

      【讨论】:

      • 我硬编码该值只是为了在示例中显而易见,但您是对的——这种方法比我的更灵活。 +1
      【解决方案6】:

      我需要验证一些东西,而不仅仅是 RegEx,但仍然遇到与此类似的问题。我的方法是为我可能已经拥有的任何自定义路由约束编写一个约束包装器:

      public class OptionalRouteConstraint : IRouteConstraint
      {
          public IRouteConstraint Constraint { get; set; }
      
          public bool Match
              (
                  HttpContextBase httpContext,
                  Route route,
                  string parameterName,
                  RouteValueDictionary values,
                  RouteDirection routeDirection
              )
          {
              var value = values[parameterName];
      
              if (value != UrlParameter.Optional)
              {
                  return Constraint.Match(httpContext, route, parameterName, values, routeDirection);
              }
              else
              {
                  return true;
              }
          }
      }
      

      然后,在constraintsRouteConfig.cs 的路由下,它看起来像这样:

      defaults: new {
          //... other params
          userid = UrlParameter.Optional
      }
      constraints: new
      {
          //... other constraints
          userid = new OptionalRouteConstraint { Constraint = new UserIdConstraint() }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-28
        • 1970-01-01
        相关资源
        最近更新 更多