ASP.NET MVC: percorso con parametro opzionale, ma se fornito, deve corrispondere a \ d +

Sto provando a scrivere una rotta con un int per nullable. Dovrebbe essere ansible andare a entrambi /profile/ ma anche /profile/\d+ .

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

Come puoi vedere, dico che userId è facoltativo ma anche che dovrebbe corrispondere all’espressione regolare \d+ . Questo non funziona e vedo perché.

Ma come dovrei build un percorso che corrisponde solo a /profile/ ma anche /profile/ seguito da un numero?

Il modo più semplice sarebbe semplicemente aggiungere un’altra route senza il parametro userId , quindi hai un 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"}); 

Per quanto ne so, l’unico altro modo per farlo è con un vincolo personalizzato. Quindi il tuo percorso diventerebbe:

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

E il codice del vincolo personalizzato sarà simile a questo:

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

Non so che NullableConstraint sia il miglior nome qui, ma dipende da te!

È ansible che qualcosa sia cambiato da quando a questa domanda è stata data risposta, ma sono stato in grado di cambiare questo:

 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 questo:

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

Semplicemente usando il * invece del + nell’espressione regolare compiuto lo stesso compito. La rotta è ancora triggers se il parametro non è stato incluso, ma se incluso si spara solo se il valore era un numero intero valido. Altrimenti fallirebbe.

ASP.NET MVC 3 ha risolto questo problema e, come evidenziato da Alex Ford , puoi utilizzare \d* invece di scrivere un vincolo personalizzato. Se il tuo pattern è più complicato, come cercare un anno con \d{4} , assicurati che il tuo pattern corrisponda a quello che vuoi e una stringa vuota, come (\d{4})? o \d{4}|^$ . Qualunque cosa ti renda felice.

Se si sta ancora utilizzando ASP.NET MVC 2 e si desidera utilizzare l’esempio di Mark Bell o l’ esempio di NYCChris , tenere presente che il percorso corrisponderà a condizione che il parametro URL contenga una corrispondenza con il modello. Ciò significa che il pattern \d+ abc123def parametri come abc123def . Per evitare ciò, avvolgere il pattern con ^( e )$ o quando si definiscono i percorsi o nel vincolo personalizzato. (Se si guarda System.Web.Routing.Route.ProcessConstraint in Reflector , si vedrà che lo fa automaticamente quando si utilizza il vincolo incorporato, inoltre imposta le opzioni CultureInvariant, Compiled e IgnoreCase .)

Dato che ho già scritto il mio vincolo personalizzato con il comportamento predefinito sopra menzionato prima di rendermi conto che non dovevo usarlo, lo lascerò qui:

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

Ed ecco un esempio di percorso:

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

la tua regex dovrebbe essere \ d *?

Grazie a Mark Bell per questa risposta, mi ha aiutato un bel po ‘.

Mi chiedo perché hai codificato il controllo per “userId” nel vincolo? Ho riscritto leggermente la tua class come utente del parameterName parameterName e sembra che stia funzionando bene.

Mi manca qualcosa facendo così?

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

Avevo bisogno di convalidare alcune cose con più di una semplice RegEx, ma ottenevo ancora un problema simile a questo. Il mio approccio era quello di scrivere un wrapper vincolo per tutti i vincoli del percorso personalizzato che potrei già avere:

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

E poi, in constraints sotto una rotta in RouteConfig.cs , sarebbe simile a questo:

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