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

时间:2009-01-19 16:49:57

标签: asp.net-mvc routing query-string

使用ASP.NET MVC Preview 5(尽管已经尝试使用Beta),路由中的查询字符串默认值会覆盖查询字符串中传递的值。一个repro就是写一个像这样的控制器:

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

路由映射如下:

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

然后使用此相对URI调用它:

/Test/Foo?x=5

我看到的跟踪输出是:

1
5

因此换句话说,为路径设置的默认值总是传递给方法,而不管它是否实际上是在查询字符串上提供的。请注意,如果删除了查询字符串的默认值,即路由映射如下:

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

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

5
5

这对我来说就像一个bug,但我会发现非常令人惊讶的是,像这样的bug仍然可以在ASP.NET MVC框架的beta版本中,因为具有默认值的查询字符串并不完全是一个深奥或边缘 - 功能,所以这几乎肯定是我的错。我有什么想法吗?

4 个答案:

答案 0 :(得分:30)

使用QueryStrings查看ASP.NET MVC的最佳方法是将它们视为路由不知道的值。如您所知,QueryString不是RouteData的一部分,因此,您应该将您传递的内容保存为与路由值分开的查询字符串。

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

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

/Test/Foo or /Test/Foo/5

并且路线应如下所示:

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

获得您正在寻找的行为。

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

/Test/Foo/5?page=1

你的行动应该像这样改变:

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

现在测试:

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

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

答案 1 :(得分:15)

我的一位同事发现a link which indicates that this is by design,该文章的作者raised an issue with the MVC team表示这是对早期版本的更改。他们的反应如下(对于“页面”,您可以阅读“x”以使其与上述问题相关):

  

这是设计的。路由没有   关注查询字符串   值;它仅关注自身   来自RouteData的值。你应该   而是删除“页面”的条目   来自Defaults字典,以及   动作方法本身或在   过滤器设置默认值   “page”如果尚未设置。

     我们希望将来有一个   将参数标记为更简单的方法   显然来自RouteData,   查询字符串或表单。在那之前   实施上述解决方案应该   工作。如果有,请告诉我们   不!

所以看起来这种行为是'正确的',但是它与principle of least astonishment非常正交,我仍然不能相信它。


编辑#1:请注意,该帖子详细说明了如何提供默认值的方法,但是这不再有效,因为他用来访问ActionMethod的{​​{1}}属性已被删除ASP.NET MVC的版本。我目前正在开发替代方案,并在完成后发布。


编辑#2:我已经更新了链接帖子中的想法,以便与ASP.NET MVC的Preview 5版本一起使用,我相信它也应该与Beta版本一起使用,虽然我不能保证它,因为我们没有尚未转移到那个版本。这很简单,我刚刚在这里发布了它。

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

然后你可以简单地将它应用于查询字符串中的方法参数,例如。

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

像魅力一样!

答案 2 :(得分:0)

我认为查询字符串参数不会覆盖默认值的原因是阻止人们破解网址。

有人可以使用其查询字符串包含控制器,操作或其他默认值的网址,您不希望它们更改。

我通过做@ Dale-Ragan建议并在action方法中处理它来处理这个问题。适合我。

答案 3 :(得分:-3)

我认为MVC中的Routing是要摆脱查询字符串。像这样:

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