読者です 読者をやめる 読者になる 読者になる

miso_soup3 Blog

主に ASP.NET 関連について書いています。

ValueProviderを扱うときに気をつけること

MVCでは、いろんなHTTPリクエストの内容を取得することができます。(MVCに限らずですが)
クエリ文字列、フォーム送信データ、Cookie、ヘッダー情報、など…。
これらの値は、モデルバインド時にも使用されます。

リクエストの値を扱う時に、知っておきたいこと&気をつけたいことを記述します。

試し1

↓このようなルーティングを行い、

routes.MapRoute(
 "AboutRouting",
 "Home/About/{key}",
 new { controller = "Home", action = "About", key = "default"}
);

↓このURLでGETを行った場合、

//localhost:4624/Home/About/tokyo/?key=jungle

↓このアクションメソッドにたどり着きますが、

public ActionResult About(String key)
{
    var model = new ValueViewModel();

    model.Key = key;
    model.ByRequestParams = this.Request.Params["key"];
    model.ByRouteData = this.RouteData.Values["key"].ToString();
    model.ByValueProvider = this.ValueProvider.GetValue("key").AttemptedValue;

    return View(model);
}

各プロパティには、どんな値が入るでしょうか?
どれも"key"という名前をもとに取得しています。

結果はこれです。

this.Request.Params["key"]だけ、クエリ文字列が帰ってきています。

試し2

今度は、POSTです。
先ほどと同じルーティング、アクションメソッドで、
Viewに"key"という名前のinput要素を配置し、POSTしてみます。

submitボタンを押してPOSTした後の結果はこれです。

取得できる値が違う

MVCに慣れると( ´_ゝ`)フーン という結果ですが、

上記の2つの例で、取得した値がそれぞれで違うのは、

  • 取得できる値の種類が違うこと
  • 値の検索方法が違うこと

が理由です。

Request.Params["name"]

このRequestオブジェクトで取得できるものは、以下の通りです。

  • クエリ文字列
  • フォーム送信データ
  • Cookie
  • ServerVariables

上から下に検索し、値をすべて取得します。
ルートデータの値は取得できません。
また、大小文字の区別はありません。

RouteData.Values["key"]

RouteDataからは、

  • ルートデータ

しか取得できません。
Requestと同じく、大小文字の区別はありません。

ValueProvider.GetValue("key")

ValueProviderは、Requestオブジェクト・RouteData、や、その他いろいろな値を
まとめて持つためのコンテナーです。

このValueProviderはモデルバインド時に利用されています。
なので、先ほどのお試し2にて、引数のkeyと、ValueProviderから取得した値が同じだったのは、
引数のkeyが、ValueProviderによって解決されたためです。(指揮系統はモデルバインダーさんです。)

取得できる値の種類は、開発者がカスタマイズできますが、
既存では、以下の通りになっています。

  • 子アクションの値
  • フォーム送信データ
  • ルートデータ
  • クエリ文字列
  • 送信されたファイル

上から順に名前を照合し、値を取得します。
MVCを利用していて、GET、POSTに限らず自然に値を取得できるのは、このロジックのお陰です。

コントローラでウォッチすると、それぞれのValueProviderを確認することができます。

また、このValueProviderでは、大小文字を区別して値を取得しています。

ValueProviderFactory を追加するときに気をつけること

上のValueProviderに加え、CookieやJson、Sessionデータを値取得の対象としたい場合は、
Global.asax.csに、以下の任意のコードを追加します。
(各ProviderFactoryの名前でぐぐると、詳細な記事がたくさんあります。)

ValueProviderFactories.Factories.Add(new CookieValueProviderFactory());
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new TempDataValueProviderFactory());
ValueProviderFactories.Factories.Add(new SessionValueProviderFactory());

追加した場合、値の照合順番は最後になります。

が、CookieやSessionを扱う時は、注意が必要です。
その理由は、すべてのアクションメソッドでSessionやCookieが値バインドの対象になり得るからです。

実際、これで、軽いバグを引き起こしました。

例えば、引数にモデルクラスを指定したアクションメソッドがあるとします。

規定のモデルバインダーは、引数にインスタンスを入れてくれますが、
もしValueProviderが"model"という名前の何か(Cookieとか)を持っていた場合は、
モデルバインダーがその値を引数にバインドしようとします。
開発者の予期しないバインドだった場合、バインドは失敗し、引数は null になります。

ValueProviderとモデルバインダーは、うまく使えば大幅な工数削減になるので、
うまく使えるようになりたいです…。