2010-10-05 10 views
21

Sto provando a scrivere una rotta con un valore nullable int in essa. Dovrebbe essere possibile accedere a entrambi gli /profile/ ma anche a /profile/\d+.ASP.NET MVC: percorso con parametro facoltativo, ma se fornito, deve corrispondere d +

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

Come potete vedere, io dico che userId è facoltativo ma anche che deve corrispondere l'espressione regolare \d+. Questo non funziona e vedo perché.

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

risposta

26

Il più semplice modo sarebbe quella di aggiungere solo un altro percorso senza il parametro userId, in modo da avere un ripiego:

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 si può fare questo sarebbe con un vincolo personalizzato. Quindi il percorso diventerebbe:

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

E il codice vincolo personalizzato sarà simile a questa:

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 è il miglior nome qui, ma che sta a voi!

+0

Ho creato qualcosa di simile a questo, ma leggermente più generico, con un costruttore che utilizza un'espressione regolare per confrontarsi con il valore fornito. Grazie! –

+0

So che arrivo molto tardi qui, ma non dovrei restituire l'ultima riga vero? In caso contrario, il vincolo non riesce sempre se la direzione del percorso è UrlGeneration. –

+0

Se l'ultima riga è restituita vera, * qualsiasi cosa * corrisponde al vincolo ... ma sì, hai ragione, questo non prende in considerazione altro che il routing delle richieste in entrata. Guardandolo ora, avrei pensato di rimuovere la parte routeDirection dell'istruzione 'if' del tutto; Verificherò domani e modificherò se questo è il caso. –

3

se la tua espressione regolare è \ d *?

+0

Con '\ d *' non corrisponde a tutti, sia con o senza 'UrlParameter.Optional'. –

+3

@DenizDogan Non sono sicuro se qualcosa è cambiato da allora ma ho appena testato '\ d *' e ha realizzato esattamente la stessa cosa di 'OptionalRegExConstraint'. – Chev

2

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 classe come utente del parametro parameterName e sembra che stia funzionando bene.

Mi manca qualcosa facendolo in questo modo?

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

Ho codificato questo valore solo per motivi di ovvietà nell'esempio, ma hai ragione: questo approccio è più flessibile del mio. +1 –

13

E 'possibile qualcosa è cambiato da quando la questione è stata risolta, 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 * al posto del + in l'espressione regolare ha compiuto lo stesso compito. La rotta è ancora attiva se il parametro non è stato incluso, ma se incluso si spara solo se il valore era un numero intero valido. Altrimenti fallirebbe.

+0

Funziona per me. Grazie. –

7

ASP.NET MVC 3 ha risolto questo problema e come Alex Ford brought out, è possibile utilizzare \d* anziché scrivere un vincolo personalizzato. Se il tuo schema è più complicato, ad esempio cerca un anno con \d{4}, assicurati che il modello corrisponda a ciò che desideri e una stringa vuota, ad esempio (\d{4})? o \d{4}|^$. Qualunque cosa ti renda felice.

Se si sta ancora utilizzando ASP.NET MVC 2 e si desidera utilizzare o Mark Bell's exampleNYCChris' example, si prega di essere consapevole del fatto che la rotta corrisponderà fino a quando il parametro URL contiene una corrispondenza per il vostro modello.Ciò significa che il modello \d+ corrisponderà a parametri come abc123def. Per evitare ciò, avvolgere il motivo con ^( e )$ o quando si definiscono i percorsi o nel vincolo personalizzato. (Se si guarda alla System.Web.Routing.Route.ProcessConstraint in Reflector, vedrai che lo fa per voi quando si utilizza il costruito nel vincolo. Essa stabilisce anche le opzioni CultureInvariant, Compiled, and IgnoreCase.)

Dal momento che ho già scritto il mio vincolo personalizzato con il comportamento di default di cui sopra prima di rendersi conto che non ho dovuto usarlo, lascio 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 percorso esempio:

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

ASP.NET MVC 4 consente anche elegantemente espressioni regolari che corrispondono a stringhe vuote come '(\ d {4})?', Il che mi rende felice. –

+1

Forse è ora che questa risposta sia contrassegnata come corretta? –

Problemi correlati