ASP.NET MVC: Route with optional parameter, but if supplied, must match \d+

前端 未结 6 1301
孤街浪徒
孤街浪徒 2020-11-29 12:00

I\'m trying to write a route with a nullable int in it. It should be possible to go to both /profile/ but also /profile/\\d+.

route         


        
相关标签:
6条回答
  • 2020-11-29 12:26

    should your regex be \d*?

    0 讨论(0)
  • 2020-11-29 12:32

    It's possible something changed since this question was answered but I was able to change this:

    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+")
        })
    );
    

    With this:

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

    Simply using the * instead of the + in the regular expression accomplished the same task. The route still fired if the parameter was not included, but if included it would only fire if the value was a valid integer. Otherwise it would fail.

    0 讨论(0)
  • 2020-11-29 12:33

    ASP.NET MVC 3 has solved this problem, and as Alex Ford brought out, you can use \d* instead of writing a custom constraint. If your pattern is more complicated, like looking for a year with \d{4}, just make sure your pattern matches what you want as well as an empty string, like (\d{4})? or \d{4}|^$. Whatever makes you happy.

    If you are still using ASP.NET MVC 2 and want to use Mark Bell's example or NYCChris' example, please be aware that the route will match as long as the URL parameter contains a match to your pattern. This means that the pattern \d+ will match parameters like abc123def. To avoid this, wrap the pattern with ^( and )$ either when defining your routes or in the custom constraint. (If you look at System.Web.Routing.Route.ProcessConstraint in Reflector, you'll see that it does this for you when using the built in constraint. It also sets the CultureInvariant, Compiled, and IgnoreCase options.)

    Since I already wrote my own custom constraint with the default behavior mentioned above before realizing I didn't have to use it, I'll leave it here:

    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;
      }
    }
    

    And here's an example route:

    routes.MapRoute("PostsByDate",
                    "{year}/{month}",
                    new { controller = "Posts",
                          action = "ByDate",
                          month = UrlParameter.Optional },
                    new { year = @"\d{4}",
                          month = new OptionalConstraint(@"\d\d") });
    
    0 讨论(0)
  • 2020-11-29 12:35

    Thanks to Mark Bell for this answer, it helped me quite a bit.

    I'm wondering why you hard coded the check for "userId" in the constraint? I slightly rewrote your class like to user the parameterName parameter, and it seems to be working just fine.

    Am I missing anything by doing it this way?

    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;
        }
    }
    
    0 讨论(0)
  • 2020-11-29 12:42

    The simplest way would be to just add another route without the userId parameter, so you have a fallback:

    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"});
    

    As far as I know, the only other way you can do this would be with a custom constraint. So your route would become:

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

    And the custom constraint code will look like this:

    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;
            }
        }
    }
    

    I don't know that NullableConstraint is the best name here, but that's up to you!

    0 讨论(0)
  • 2020-11-29 12:42

    I needed to validate a few things with more than just a RegEx but was still getting an issue similar to this. My approach was to write a constraint wrapper for any custom route constraints I may already have:

    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;
            }
        }
    }
    

    And then, in constraints under a route in RouteConfig.cs, it would look like this:

    defaults: new {
        //... other params
        userid = UrlParameter.Optional
    }
    constraints: new
    {
        //... other constraints
        userid = new OptionalRouteConstraint { Constraint = new UserIdConstraint() }
    }
    
    0 讨论(0)
提交回复
热议问题