ASP。NET MVC QueryString默认覆盖提供的值?

时间:2022-02-23 15:12:11

Using ASP.NET MVC Preview 5 (though this has also been tried with the Beta), it appears that querystring defaults in a route override the value that is passed in on the query string. A repro is to write a controller like this:

使用ASP。NET MVC预览5(虽然这也已经尝试过了),但是在一个路径中,querystring的默认值会覆盖在查询字符串中传递的值。repro就是这样写一个控制器:

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

With route mapped as follows:

路线图如下:

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

And then invoke it with this relative URI:

然后用相对URI调用它:

/Test/Foo?x=5

The trace output I see is:

我看到的跟踪输出是:

1
5

So in other words the default value that was set up for the route is always passed into the method, irrespective of whether it was actually supplied on the querystring. Note that if the default for the querystring is removed, i.e. the route is mapped as follows:

因此,换句话说,为路由设置的默认值总是被传递到方法中,而不管它是否在querystring上提供。注意,如果移除querystring的默认值,即路由映射如下:

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

Then the controller behaves as expected and the value is passed in as the parameter value, giving the trace output:

然后控制器按预期运行,值作为参数值传入,输出跟踪:

5
5

This looks to me like a bug, but I would find it very surprising that a bug like this could still be in the beta release of the ASP.NET MVC framework, as querystrings with defaults aren't exactly an esoteric or edge-case feature, so it's almost certainly my fault. Any ideas what I'm doing wrong?

在我看来,这是一个bug,但是我发现这样的bug仍然会出现在ASP的beta版本中,这是非常令人惊讶的。NET MVC框架,作为带有默认值的查询字符串并不是一个深奥的或边缘的特性,所以几乎可以肯定这是我的错。你知道我做错了什么吗?

4 个解决方案

#1


30  

The best way to look at ASP.NET MVC with QueryStrings is to think of them as values that the route does not know about. As you found out, the QueryString is not part of the RouteData, therefore, you should keep what you are passing as a query string separate from the route values.

看待ASP的最佳方式。带有querystring的NET MVC将它们视为路由不知道的值。正如您所发现的,QueryString并不是RouteData的一部分,因此,您应该将您所传递的作为查询字符串与路由值分隔开。

A way to work around them is to create default values yourself in the action if the values passed from the QueryString are null.

解决这些问题的一种方法是,如果从QueryString传递的值为空,则在操作中自己创建默认值。

In your example, the route knows about x, therefore your url should really look like this:

在您的示例中,路由知道x,因此您的url应该如下所示:

/Test/Foo or /Test/Foo/5

and the route should look like this:

路线应该是这样的:

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

To get the behavior you were looking for.

为了得到你想要的行为。

If you want to pass a QueryString value, say like a page number then you would do this:

如果您想传递一个QueryString值,比如一个页码,那么您可以这样做:

/Test/Foo/5?page=1

And your action should change like this:

你的行为应该这样改变:

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

Now the 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

Hope this helps clarify some things.

希望这有助于澄清一些事情。

#2


15  

One of my colleagues found a link which indicates that this is by design and it appears the author of that article raised an issue with the MVC team saying this was a change from earlier releases. The response from them was below (for "page" you can read "x" to have it relate to the question above):

我的一个同事发现了一个链接,表明这是设计出来的,而且这篇文章的作者似乎向MVC团队提出了一个问题,说这是早期版本的一个改变。他们的回答如下(对于“页”,你可以读“x”,让它与上面的问题相关):

This is by design. Routing does not concern itself with query string values; it concerns itself only with values from RouteData. You should instead remove the entry for "page" from the Defaults dictionary, and in either the action method itself or in a filter set the default value for "page" if it has not already been set.

这是通过设计。路由与查询字符串值无关;它只关注路由数据的值。相反,您应该从默认字典中删除“page”条目,如果还没有设置“page”,则在操作方法本身或过滤器中设置“page”的默认值。

We hope to in the future have an easier way to mark a parameter as explicitly coming from RouteData, the query string, or a form. Until that is implemented the above solution should work. Please let us know if it doesn't!

我们希望将来能有一种更简单的方法来将参数明确地标记为来自RouteData、查询字符串或表单的参数。在实现这一点之前,上述解决方案应该是有效的。如果没有,请告诉我们!

So it appears that this behaviour is 'correct', however it is so orthogonal to the principle of least astonishment that I still can't quite believe it.

因此,这种行为似乎是“正确的”,然而,它与最不惊讶的原则是如此的正交,以至于我仍然不能完全相信它。


Edit #1: Note that the post details a method of how to provide default values, however this no longer works as the ActionMethod property he uses to access the MethodInfo has been removed in the latest version of ASP.NET MVC. I'm currently working on an alternative and will post it when done.

编辑#1:注意,这篇文章详细介绍了如何提供默认值的方法,但是这不再适用于他用来访问method的ActionMethod属性,因为它在最新的ASP版本中已经被删除了。净MVC。我目前正在研究另一种选择,完成后会发布。


Edit #2: I've updated the idea in the linked post to work with the Preview 5 release of ASP.NET MVC and I believe it should also work with the Beta release though I can't guarantee it as we haven't moved to that release yet. It's so simple that I've just posted it inline here.

编辑#2:我已经在链接的文章中更新了这个想法,以配合ASP的预览5版本。NET MVC和我相信它也应该与Beta版本一起工作,虽然我不能保证它,因为我们还没有移动到那个版本。它是如此简单,我只是把它内联在这里。

First there's the default attribute (we can't use the existing .NET DefaultValueAttribute as it needs to inherit from CustomModelBinderAttribute):

首先是默认属性(我们不能使用现有的。net DefaultValueAttribute,因为它需要从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);
    }
}

The the custom binder:

自定义绑定器:

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

And then you can simply apply it to the method parameters that come in on the querystring, e.g.

然后你可以将它应用到querystring上的方法参数,例如。

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

Works like a charm!

就像一个魅力!

#3


0  

I think the reason querystring parameters do not override the defaults is to stop people hacking the url.

我认为querystring参数不覆盖默认值的原因是阻止人们入侵url。

Someone could use a url whose querystring included controller, action or other defaults you didn't want them to change.

有些人可以使用一个url,它的querystring包含控制器、操作或其他您不希望它们更改的默认值。

I've dealt with this problem by doing what @Dale-Ragan suggested and dealing with it in the action method. Works for me.

我已经按照@Dale-Ragan的建议处理了这个问题,并在action方法中处理它。为我工作。

#4


-3  

I thought the point with Routing in MVC is to get rid of querystrings. Like this:

我认为在MVC中使用路由的目的是消除查询字符串。是这样的:

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

#1


30  

The best way to look at ASP.NET MVC with QueryStrings is to think of them as values that the route does not know about. As you found out, the QueryString is not part of the RouteData, therefore, you should keep what you are passing as a query string separate from the route values.

看待ASP的最佳方式。带有querystring的NET MVC将它们视为路由不知道的值。正如您所发现的,QueryString并不是RouteData的一部分,因此,您应该将您所传递的作为查询字符串与路由值分隔开。

A way to work around them is to create default values yourself in the action if the values passed from the QueryString are null.

解决这些问题的一种方法是,如果从QueryString传递的值为空,则在操作中自己创建默认值。

In your example, the route knows about x, therefore your url should really look like this:

在您的示例中,路由知道x,因此您的url应该如下所示:

/Test/Foo or /Test/Foo/5

and the route should look like this:

路线应该是这样的:

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

To get the behavior you were looking for.

为了得到你想要的行为。

If you want to pass a QueryString value, say like a page number then you would do this:

如果您想传递一个QueryString值,比如一个页码,那么您可以这样做:

/Test/Foo/5?page=1

And your action should change like this:

你的行为应该这样改变:

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

Now the 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

Hope this helps clarify some things.

希望这有助于澄清一些事情。

#2


15  

One of my colleagues found a link which indicates that this is by design and it appears the author of that article raised an issue with the MVC team saying this was a change from earlier releases. The response from them was below (for "page" you can read "x" to have it relate to the question above):

我的一个同事发现了一个链接,表明这是设计出来的,而且这篇文章的作者似乎向MVC团队提出了一个问题,说这是早期版本的一个改变。他们的回答如下(对于“页”,你可以读“x”,让它与上面的问题相关):

This is by design. Routing does not concern itself with query string values; it concerns itself only with values from RouteData. You should instead remove the entry for "page" from the Defaults dictionary, and in either the action method itself or in a filter set the default value for "page" if it has not already been set.

这是通过设计。路由与查询字符串值无关;它只关注路由数据的值。相反,您应该从默认字典中删除“page”条目,如果还没有设置“page”,则在操作方法本身或过滤器中设置“page”的默认值。

We hope to in the future have an easier way to mark a parameter as explicitly coming from RouteData, the query string, or a form. Until that is implemented the above solution should work. Please let us know if it doesn't!

我们希望将来能有一种更简单的方法来将参数明确地标记为来自RouteData、查询字符串或表单的参数。在实现这一点之前,上述解决方案应该是有效的。如果没有,请告诉我们!

So it appears that this behaviour is 'correct', however it is so orthogonal to the principle of least astonishment that I still can't quite believe it.

因此,这种行为似乎是“正确的”,然而,它与最不惊讶的原则是如此的正交,以至于我仍然不能完全相信它。


Edit #1: Note that the post details a method of how to provide default values, however this no longer works as the ActionMethod property he uses to access the MethodInfo has been removed in the latest version of ASP.NET MVC. I'm currently working on an alternative and will post it when done.

编辑#1:注意,这篇文章详细介绍了如何提供默认值的方法,但是这不再适用于他用来访问method的ActionMethod属性,因为它在最新的ASP版本中已经被删除了。净MVC。我目前正在研究另一种选择,完成后会发布。


Edit #2: I've updated the idea in the linked post to work with the Preview 5 release of ASP.NET MVC and I believe it should also work with the Beta release though I can't guarantee it as we haven't moved to that release yet. It's so simple that I've just posted it inline here.

编辑#2:我已经在链接的文章中更新了这个想法,以配合ASP的预览5版本。NET MVC和我相信它也应该与Beta版本一起工作,虽然我不能保证它,因为我们还没有移动到那个版本。它是如此简单,我只是把它内联在这里。

First there's the default attribute (we can't use the existing .NET DefaultValueAttribute as it needs to inherit from CustomModelBinderAttribute):

首先是默认属性(我们不能使用现有的。net DefaultValueAttribute,因为它需要从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);
    }
}

The the custom binder:

自定义绑定器:

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

And then you can simply apply it to the method parameters that come in on the querystring, e.g.

然后你可以将它应用到querystring上的方法参数,例如。

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

Works like a charm!

就像一个魅力!

#3


0  

I think the reason querystring parameters do not override the defaults is to stop people hacking the url.

我认为querystring参数不覆盖默认值的原因是阻止人们入侵url。

Someone could use a url whose querystring included controller, action or other defaults you didn't want them to change.

有些人可以使用一个url,它的querystring包含控制器、操作或其他您不希望它们更改的默认值。

I've dealt with this problem by doing what @Dale-Ragan suggested and dealing with it in the action method. Works for me.

我已经按照@Dale-Ragan的建议处理了这个问题,并在action方法中处理它。为我工作。

#4


-3  

I thought the point with Routing in MVC is to get rid of querystrings. Like this:

我认为在MVC中使用路由的目的是消除查询字符串。是这样的:

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