ASP.NET MVC: ruta con parámetro opcional, pero si se proporciona, debe coincidir con \ d +

Estoy intentando escribir una ruta con un int nullable en ella. Debería ser posible ir a /profile/ pero también /profile/\d+ .

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

Como puede ver, digo que userId es opcional pero también que debe coincidir con la expresión regular \d+ . Esto no funciona y veo por qué.

¿Pero cómo construiría una ruta que coincida con just /profile/ pero también /profile/ seguido de un número?

La forma más sencilla sería simplemente agregar otra ruta sin el parámetro userId , por lo que tiene una alternativa:

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

Por lo que yo sé, la única otra forma en que puedes hacer esto sería con una restricción personalizada. Entonces tu ruta sería:

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

Y el código de restricción personalizado se verá así:

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

No sé que NullableConstraint es el mejor nombre aquí, ¡pero eso depende de usted!

Es posible que algo haya cambiado desde que se respondió esta pregunta, pero pude cambiar esto:

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

Con este:

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

Simplemente usando el * lugar del + en la expresión regular se logra la misma tarea. La ruta aún se activaba si el parámetro no estaba incluido, pero si se incluía, solo se activaba si el valor era un número entero válido. De lo contrario, fallaría.

ASP.NET MVC 3 ha resuelto este problema y, como señaló Alex Ford , puede usar \d* lugar de escribir una restricción personalizada. Si su patrón es más complicado, como buscar un año con \d{4} , simplemente asegúrese de que su patrón coincida con lo que desea, así como una cadena vacía, como (\d{4})? o \d{4}|^$ . Lo que te haga feliz.

Si todavía está utilizando ASP.NET MVC 2 y desea utilizar el ejemplo de Mark Bell o el ejemplo de NYCChris , tenga en cuenta que la ruta coincidirá siempre que el parámetro de URL contenga una coincidencia con su patrón. Esto significa que el patrón \d+ coincidirá con parámetros como abc123def . Para evitar esto, ajuste el patrón con ^( y )$ al definir sus rutas o en la restricción personalizada. (Si observa System.Web.Routing.Route.ProcessConstraint en Reflector , verá que lo hace por usted cuando usa la restricción incorporada. También establece las opciones CultureInvariant, Compiled e IgnoreCase ).

Como ya escribí mi propia restricción personalizada con el comportamiento predeterminado mencionado anteriormente antes de darme cuenta de que no tenía que usarla, la dejo aquí:

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

Y aquí hay una ruta de ejemplo:

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

¿debería tu expresión regular ser \ d *?

Gracias a Mark Bell por esta respuesta, me ayudó bastante.

Me pregunto por qué codificaste duro el cheque para “userId” en la restricción. Reescribí ligeramente su clase como para usar el parameterName parameterName, y parece estar funcionando bien.

¿Me estoy perdiendo algo al hacerlo de esta manera?

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

Necesitaba validar algunas cosas con algo más que solo un RegEx, pero todavía recibía un problema similar a esto. Mi enfoque fue escribir un contenedor de restricciones para cualquier restricción de ruta personalizada que pueda tener:

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

Y luego, en constraints bajo una ruta en RouteConfig.cs , se vería así:

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