web-dev-qa-db-ja.com

ASP.NET MVC QueryStringのデフォルトは、指定された値をオーバーライドしますか?

ASP.NET MVCプレビュー5を使用すると(これはベータ版でも試されていますが)、ルートのクエリ文字列のデフォルトは、クエリ文字列で渡される値を上書きするようです。再現は、次のようなコントローラーを作成することです。

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

これはバグのように見えますが、デフォルトのクエリ文字列は厳密には難解な機能やエッジケース機​​能ではないため、このようなバグがASP.NETMVCフレームワークのベータリリースに残っている可能性があることは非常に驚くべきことです。 、だからそれはほぼ間違いなく私のせいです。私が間違っていることについて何か考えはありますか?

25
Greg Beech

QueryStringsを使用してASP.NETMVCを確認する最良の方法は、ルートが認識していない値としてそれらを考えることです。ご存知のとおり、QueryStringはRouteDataの一部ではないため、クエリ文字列として渡すものをルート値とは別に保持する必要があります。

それらを回避する方法は、QueryStringから渡された値がnullの場合、アクションで自分でデフォルト値を作成することです。

あなたの例では、ルートはxについて知っているので、URLは実際には次のようになります。

/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

これがいくつかのことを明確にするのに役立つことを願っています。

30
Dale Ragan

私の同僚の1人が これは仕様によるものであることを示すリンク そしてその記事の作者のようです MVCチームに問題を提起しました これは以前からの変更であると言っていますリリース。彼らからの回答は以下のとおりです(「ページ」については、「x」を読んで上記の質問に関連させることができます):

これは仕様によるものです。ルーティングは、クエリ文字列値とは関係ありません。それはRouteDataからの値にのみ関係します。代わりに、デフォルトディクショナリから「ページ」のエントリを削除する必要があります。アクションメソッド自体またはフィルタで、「ページ」のデフォルト値がまだ設定されていない場合は設定します。

将来的には、RouteData、クエリ文字列、またはフォームから明示的に取得されたものとしてパラメータをマークする簡単な方法があることを望んでいます。それが実装されるまで、上記のソリューションは機能するはずです。そうでない場合はお知らせください。

したがって、この動作は「正しい」ように見えますが、 驚き最小の原則 に非常に直交しているため、まだ完全には信じられません。


編集#1:投稿にはデフォルト値を提供する方法の詳細が記載されていますが、ActionMethodへのアクセスに使用するMethodInfoプロパティが最新バージョンで削除されたため、これは機能しなくなりました。 ASP.NETMVCの私は現在代替案に取り組んでおり、完了したら投稿します。


編集#2:リンクされた投稿のアイデアをASP.NET MVCのプレビュー5リリースで動作するように更新しました。移動していないため保証できませんが、ベータリリースでも動作するはずです。まだそのリリースに。とてもシンプルなので、ここにインラインで投稿しました。

まず、デフォルトの属性があります(DefaultValueAttributeから継承する必要があるため、既存の.NET 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);
    }
}

そして、クエリ文字列に含まれるメソッドパラメータに簡単に適用できます。

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

チャームのように機能します!

15
Greg Beech

クエリ文字列パラメータがデフォルトを上書きしない理由は、人々がURLをハッキングするのを防ぐためだと思います。

誰かが、クエリ文字列にコントローラー、アクション、または変更したくないその他のデフォルトが含まれているURLを使用する可能性があります。

@ Dale-Raganが提案したことを実行し、アクションメソッドで処理することで、この問題に対処しました。私のために働きます。

0
Richard Garside