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

我的未来我决定 提交于 2019-11-26 09:58:20

问题


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+.

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

As you can see, I say that userId is optional but also that it should match the regular expression \\d+. This does not work and I see why.

But how would I construct a route that matches just /profile/ but also /profile/ followed by a number?


回答1:


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!




回答2:


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.




回答3:


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



回答4:


should your regex be \d*?




回答5:


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



回答6:


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() }
}


来源:https://stackoverflow.com/questions/3862336/asp-net-mvc-route-with-optional-parameter-but-if-supplied-must-match-d

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!