web-dev-qa-db-fra.com

ASP.NET MVC: Une route avec un paramètre facultatif, mais si elle est fournie, doit correspondre à\d +

J'essaie d'écrire une route avec un int nullable. Il devrait être possible d'aller à la fois /profile/ mais aussi /profile/\d+.

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

Comme vous pouvez le constater, je dis que userId est facultatif, mais qu’elle doit également correspondre à l’expression régulière \d+. Cela ne fonctionne pas et je vois pourquoi.

Mais comment pourrais-je construire un itinéraire qui correspond à /profile/, mais aussi à /profile/ suivi d'un nombre?

22
Deniz Dogan

La méthode le plus simple consisterait simplement à ajouter une autre route sans le paramètre userId, de sorte que vous disposiez d'un repli:

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

Autant que je sache, la seule autre façon de procéder serait avec une contrainte personnalisée. Ainsi, votre itinéraire deviendrait:

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

Et le code de contrainte personnalisé ressemblera à ceci:

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

Je ne sais pas si NullableConstraint est le meilleur nom ici, mais c'est vous qui décidez!

28
Mark Bell

Il est possible que quelque chose ait changé depuis la réponse à cette question, mais j'ai pu changer cela:

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

Avec ça:

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

Utiliser simplement le * au lieu du + dans l'expression régulière a permis d'accomplir la même tâche. La route est toujours déclenchée si le paramètre n'est pas inclus, mais si elle est incluse, elle ne sera déclenchée que si la valeur est un entier valide. Sinon, cela échouerait.

13
Chev

ASP.NET MVC 3 a résolu ce problème, et comme Alex Ford a fait apparaître , vous pouvez utiliser \d* au lieu d'écrire une contrainte personnalisée. Si votre modèle est plus compliqué, comme rechercher une année avec \d{4}, assurez-vous simplement qu'il correspond à ce que vous souhaitez, ainsi qu'une chaîne vide, telle que (\d{4})? ou \d{4}|^$. Tout ce qui vous rend heureux.

Si vous utilisez toujours ASP.NET MVC 2 et souhaitez utiliser Exemple de Mark Bell ou Exemple de NYCChris , sachez que la route correspondra tant que le paramètre d'URL contient une correspondance à votre modèle. Cela signifie que le modèle \d+ correspondra à des paramètres tels que abc123def. Pour éviter cela, encapsulez le modèle avec ^( et )$ lors de la définition de vos itinéraires ou dans la contrainte personnalisée. (Si vous regardez System.Web.Routing.Route.ProcessConstraint in Reflector , vous verrez que cela le fait pour vous lorsque vous utilisez la contrainte intégrée. Il définit également le CultureInvariant, Compilé et IgnoreCase options.)

Puisque j'ai déjà écrit ma propre contrainte personnalisée avec le comportement par défaut mentionné ci-dessus avant de réaliser que je n'avais pas à l'utiliser, je le laisse ici:

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

Et voici un exemple d'itinéraire:

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

votre regex doit-il être\d *?

3
Anthony Johnston

Merci à Mark Bell pour cette réponse, cela m’a beaucoup aidé.

Je me demande pourquoi vous avez codé le chèque pour "userId" dans la contrainte? J'ai légèrement réécrit votre classe, comme si vous utilisiez le paramètre parameterName, et cela semble bien fonctionner.

Est-ce que je manque quelque chose en le faisant de cette façon?

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

J'avais besoin de valider un certain nombre de choses avec plus qu'un simple RegEx mais je continuais à avoir un problème similaire à celui-ci. Mon approche consistait à écrire un wrapper de contrainte pour toutes les contraintes de route personnalisées que j'ai peut-être déjà:

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

Et puis, dans constraints sous une route dans RouteConfig.cs, cela ressemblerait à ceci:

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