次のルートを検討してください。
routes.MapRoute(
"route1",
"{controller}/{month}-{year}/{action}/{user}"
);
routes.MapRoute(
"route2",
"{controller}/{month}-{year}/{action}"
);
そして、次のテスト:
テスト1
[TestMethod]
public void Test1()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("user", "user1");
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month,
year = now.Year
}),
routes, context, true);
//OK, result == /Home/10-2012/Index/user1
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year),
result);
}
テスト 2
[TestMethod]
public void Test2()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("user", "user1");
context.RouteData.Values.Add("month", now.Month + 1);
context.RouteData.Values.Add("year", now.Year);
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month,
year = now.Year
}),
routes, context, true);
//Error because result == /Home/10-2012/Index
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year),
result);
}
このテストは、リクエスト コンテキストに既にルート値があり、UrlHelper で発信 URL を生成しようとする状況をエミュレートします。
問題は (テスト 2 で提示)、予想されるルート (ここ) からすべてのセグメントの値がありroute1
、それらの一部をパラメータで置き換えようとするとrouteValues
、必要なルートが省略され、次の適切なルートが使用されることです。
したがって、テスト 1 はうまく機能します。リクエスト コンテキストには、ルート 1 の 5 つのセグメントのうち 3 つの値が既にあり、欠落している 2 つのセグメント (つまりyear
と) の値がパラメータmonth
を介して渡されるためです。routeValues
テスト 2 には、要求コンテキスト内の 5 つのセグメントすべての値があります。そして、それらの一部 (つまり、月と年) を他の値に置き換えたいと考えていますrouteValues
。しかし、ルート 1 は適切ではないようで、ルート 2 が使用されます。
なんで?ルートのどこが間違っていますか?
このような状況では、リクエスト コンテキストを手動でクリアする必要がありますか?
編集
[TestMethod]
public void Test3()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("month", now.Month.ToString());
context.RouteData.Values.Add("year", now.Year.ToString());
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month + 1,
year = now.Year + 1
}),
routes, context, true);
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index", now.Month + 1, now.Year + 1),
result);
}
このテストは物事をより混乱させます。ここでは、route2 をテストしています。そしてそれはうまくいきます!リクエスト コンテキストに 4 つのセグメントすべての値があり、他の値を に渡しますrouteValues
。生成された送信 URL は問題ありません。
したがって、問題は route1 にあります。私は何が欠けていますか?
編集
Sanderson S. Freeman A. - Pro ASP.NET MVC 3 Framework Third Editionから:
ルーティング システムは、RegisterRoutes メソッドに渡された RouteCollection オブジェクトに追加された順序でルートを処理します。各ルートが一致するかどうかが検査されます。これには、次の 3 つの条件が満たされている必要があります。
- URL パターンで定義されたすべてのセグメント変数で、値を使用できる必要があります。各セグメント変数の値を見つけるために、ルーティング システムは最初に(匿名型のプロパティを使用して) 提供された値を調べ、次に現在の要求の変数値を調べ、最後にルートで定義されたデフォルト値を調べます。
- セグメント変数に指定した値は、ルートで定義されたデフォルトのみの変数と一致しない場合があります。これらのルートにはデフォルト値がありません
- すべてのセグメント変数の値は、ルートの制約を満たす必要があります。これらのルートに制約はありません
したがって、匿名型で値を指定した最初の規則に従って、既定値はありません。現在のリクエストの変数値- これはリクエスト コンテキストからの値であると思います。
ルート 1 ではうまく機能しているのに、ルート 2 ではこれらの推論のどこが間違っているのでしょうか?
編集
実際、すべては単体テストからではなく、実際の mvc アプリケーションから始まりました。そこで、UrlHelper.Action メソッド (文字列、オブジェクト)を使用して発信 URL を生成しました。このメソッドはレイアウト ビュー (すべてのビューの大部分の親ビュー) で使用されるため、拡張ヘルパー メソッドに取り入れました (ビューから余分なロジックを除外するため)。この拡張メソッドは、リクエストからアクション名を抽出します。コンテキスト、引数として渡されます。リクエストコンテキストを介して現在のすべてのルート値を抽出し、それらの年と月を置き換えることができることを知っています(または、コンテキストからのすべての値を含む匿名のルート値コレクションを作成できますか)、mvc は要求コンテキストに含まれる値を自動的に考慮したため、それは不要だと思いました。したがって、アクション名のない UrlHelper.Action オーバーロードがなかったため (または、アクション名も「指定しない」ことも望んでいました)、アクション名のみを抽出し、匿名のルート値オブジェクトを介して新しい月と年を追加しました。
これは拡張メソッドです。
public static MvcHtmlString GetPeriodLink(this HtmlHelper html,
RequestContext context,
DateTime date)
{
UrlHelper urlHelper = new UrlHelper(context);
return MvcHtmlString.Create(
urlHelper.Action(
(string)context.RouteData.Values["action"],
new { year = date.Year, month = date.Month }));
}
上記のテストで説明したように、短いルート (リクエスト コンテキストにコントローラー、年と月、およびアクションのみが含まれる場合) では機能しましたが、長いルート (リクエスト コンテキストにコントローラー、年と月、アクション、およびユーザーが含まれる場合) では失敗しました)。
必要な方法でルーティングを機能させるために使用する回避策を投稿しました。
確かに知りたいのですが、なぜそのようなシナリオで回避策を講じる必要があるのでしょうか。また、これらの2つのルートの主な違いは何route1
ですかroute2
。
編集
別の発言。リクエスト コンテキストの値が typestring
である限り、型の混乱 (int と string) がないように、それらを文字列としてコンテキストに設定することにしました。この点で何が変わったのかわかりませんが、一部のルートが正しく生成されるようになりました。しかし、すべてではありません...これはまだあまり意味がありません。int
テストには文字列ではなくコンテキストがあるため、テストではなく実際のアプリケーションでこれを変更しました。
さて、route1month
が使用される条件を見つけました。コンテキスト内のとの値がyear
匿名オブジェクトで指定されたものと等しい場合にのみ使用されます。それらが異なる場合 (テストでは、これは と の両方に当てはまりますint
) string
、route2が使用されます。しかし、なぜ?
これは、実際のアプリケーションにあるものを確認します。
string
コンテキストに s がありましたが、匿名オブジェクトを介して s を提供しint
たため、何らかの形で mvc が混乱し、 を使用できませんでしroute1
た。int
匿名オブジェクトの をに変更したところ、コンテキスト内の と が匿名オブジェクトstring
の URLと等しい URL が正しく生成され始めました。一方、他のすべてはそうではありませんでした。month
year
つまり、匿名オブジェクトのプロパティはstring
、リクエスト コンテキスト内のルート値の型と一致する型である必要があります。
しかし、このルールは必須ではないようです。 Test3 のように、タイプを変更しました (上で確認できます)。それでも正しく動作します。MVC は、型自体を正しくキャストできます。
最後に、このすべての動作の説明を見つけました。以下の私の回答をご覧ください。