ASP.NET les valeurs par défaut de MVC QueryString remplacent les valeurs fournies?

En utilisant ASP.NET MVC Preview 5 (bien que cela ait également été essayé avec la version bêta), il semble que les valeurs par défaut de querystring dans une route remplacent la valeur transmise dans la chaîne de requête. Une repro est d'écrire un contrôleur comme ceci:

public class TestController : Controller
{
    public ActionResult Foo(int x)
    {
        Trace.WriteLine(x);
        Trace.WriteLine(this.HttpContext.Request.QueryString["x"]);
        return new EmptyResult();
    }
}

Avec itinéraire cartographié comme suit:

routes.MapRoute(
    "test",
    "Test/Foo",
    new { controller = "Test", action = "Foo", x = 1 });

Puis l'invoquer avec cet URI relatif:

/Test/Foo?x=5

La sortie de trace que je vois est:

1
5

Donc, en d'autres termes, la valeur par défaut qui a été configurée pour la route est toujours passée dans la méthode, indépendamment du fait qu'elle a été réellement fournie sur la chaîne de requête. Notez que si la valeur par défaut de la chaîne de requête est supprimée, c'est-à-dire que la route est mappée comme suit:

routes.MapRoute(
    "test",
    "Test/Foo",
    new { controller = "Test", action = "Foo" });

Ensuite, le contrôleur se comporte comme prévu et la valeur est passée en tant que valeur de paramètre, donnant la sortie de trace:

5
5

Cela me semble être un bug, mais je trouverais très surprenant qu'un bug comme celui-ci puisse encore être dans la version bêta du ASP.NET MVC framework, comme querystrings avec les valeurs par défaut ne sont pas exactement une fonctionnalité ésotérique ou edge-case, donc c'est presque certainement ma faute. Les idées de ce que je fais mal?

25
demandé sur Greg Beech 2009-01-19 19:49:57

4 réponses

La meilleure façon de regarder ASP.NET MVC avec QueryStrings est de les considérer comme des valeurs que la route ne connaît pas. Comme vous l'avez découvert, la chaîne de requête ne fait pas partie de RouteData, par conséquent, vous devez garder ce que vous passez en tant que chaîne de requête séparée des valeurs de route.

Un moyen de les contourner est de créer vous-même des valeurs par défaut dans l'action si les valeurs transmises par la chaîne de requête sont nulles.

Dans votre exemple, la route connaît x, donc votre url devrait vraiment ressembler à ceci:

/Test/Foo or /Test/Foo/5

Et l'itinéraire devrait ressembler à ceci:

routes.MapRoute("test", "Test/Foo/{x}", new {controller = "Test", action = "Foo", x = 1});

Pour obtenir le comportement que vous recherchez.

Si vous voulez passer une valeur QueryString, disons comme un numéro de page, alors vous feriez ceci:

/Test/Foo/5?page=1

Et votre action devrait changer comme ceci:

public ActionResult Foo(int x, int? page)
{
    Trace.WriteLine(x);
    Trace.WriteLine(page.HasValue ? page.Value : 1);
    return new EmptyResult();
}

Maintenant, le test:

Url:  /Test/Foo
Trace:
1
1

Url:  /Test/Foo/5
Trace:
5
1

Url:  /Test/Foo/5?page=2
Trace:
5
2

Url:  /Test/Foo?page=2
Trace:
1
2

J'espère que cela aidera à clarifier certaines choses.

30
répondu Dale Ragan 2009-01-19 18:36:09

Un de mes collègues a trouvé un lien qui indique que c'est par conception et il semble que l'auteur de cet article ait soulevé un problème avec l'équipe MVC en disant que c'était un changement par rapport aux versions précédentes. La réponse d'eux était ci-dessous (pour "page" vous pouvez lire " x " pour l'avoir lié à la question ci-dessus):

C'est par conception. Le routage ne fonctionne pas concerne lui - même avec la chaîne de requête les valeurs; il se préoccupe uniquement avec valeurs de RouteData. Vous devriez au lieu de cela, supprimez l'entrée pour " page" dans le dictionnaire Defaults, et dans soit la méthode d'action elle-même, soit un filtre définit la valeur par défaut pour "page" si elle n'a pas déjà été définie.

Nous espérons à l'avenir avoir un moyen plus facile de marquer un paramètre comme provenant explicitement de RouteData, le chaîne de requête ou un formulaire. Jusqu'à ce qui est mis en œuvre la solution ci-dessus devrait travail. S'il vous plaît laissez-nous savoir si c' ne fait pas!

Il semble donc que ce comportement soit "correct", cependant, il est si orthogonal au principe de moindre étonnement {[9] } Que Je ne peux toujours pas le croire.


Edit # 1: notez que le post détaille une méthode pour fournir des valeurs par défaut, mais cela ne fonctionne plus car la propriété ActionMethod qu'il utilise pour accéder au MethodInfo a été supprimée dans la dernière version de ASP.NET MVC. Je travaille actuellement sur une alternative et le posterai une fois terminé.


Edit # 2: j'ai mis à jour l'idée dans le post lié à travailler avec la version Preview 5 de ASP.NET MVC et je crois qu'il devrait également fonctionner avec la version bêta bien que je ne puisse pas le garantir car nous n'avons pas encore déménagé à cette version. C'est si simple que je viens de le poster en ligne ici.

Tout d'abord, il y a l'attribut par défaut (nous ne pouvons pas utiliser le. net existant DefaultValueAttribute car il doit hériter de CustomModelBinderAttribute):

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class DefaultAttribute : CustomModelBinderAttribute
{
    private readonly object value;

    public DefaultAttribute(object value)
    {
        this.value = value;
    }

    public DefaultAttribute(string value, Type conversionType)
    {
        this.value = Convert.ChangeType(value, conversionType);
    }

    public override IModelBinder GetBinder()
    {
        return new DefaultValueModelBinder(this.value);
    }
}

Le classeur personnalisé:

public sealed class DefaultValueModelBinder : IModelBinder
{
    private readonly object value;

    public DefaultValueModelBinder(object value)
    {
        this.value = value;
    }

    public ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        var request = bindingContext.HttpContext.Request;
        var queryValue = request .QueryString[bindingContext.ModelName];
        return string.IsNullOrEmpty(queryValue) 
            ? new ModelBinderResult(this.value) 
            : new DefaultModelBinder().BindModel(bindingContext);
    }
}

Et puis vous pouvez simplement l'appliquer aux paramètres de méthode qui entrent dans la chaîne de requête, par exemple

public ActionResult Foo([Default(1)] int x)
{
    // implementation
}

Fonctionne comme un charme!

15
répondu Greg Beech 2009-01-20 13:52:09

Je pense que la raison pour laquelle les paramètres querystring ne remplacent pas les valeurs par défaut est d'arrêter les gens de pirater l'url.

Quelqu'un pourrait utiliser une url dont querystring incluait un contrôleur, une action ou d'autres valeurs par défaut que vous ne vouliez pas modifier.

J'ai traité ce problème en faisant ce que @ Dale-Ragan a suggéré et en le traitant dans la méthode d'action. Fonctionne pour moi.

0
répondu Richard Garside 2009-10-01 10:06:29

Je pensais que le point avec le routage dans MVC est de se débarrasser des querystrings. Comme ceci:

routes.MapRoute(
    "test",
    "Test/Foo/{x}",
    new { controller = "Test", action = "Foo", x = 1 });
-3
répondu Spoike 2009-01-19 17:27:00