14

次のルートを検討してください。

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 つの条件が満たされている必要があります。

  1. URL パターンで定義されたすべてのセグメント変数で、値を使用できる必要があります。各セグメント変数の値を見つけるために、ルーティング システムは最初に(匿名型のプロパティを使用して) 提供された値を調べ、次に現在の要求の変数値を調べ、最後にルートで定義されたデフォルト値を調べます。
  2. セグメント変数に指定した値は、ルートで定義されたデフォルトのみの変数と一致しない場合があります。これらのルートにはデフォルト値がありません
  3. すべてのセグメント変数の値は、ルートの制約を満たす必要があります。これらのルートに制約はありません

したがって、匿名型で値を指定した最初の規則に従って、既定値はありません。現在のリクエストの変数値- これはリクエスト コンテキストからの値であると思います。

ルート 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) stringroute2が使用されます。しかし、なぜ?

これは、実際のアプリケーションにあるものを確認します。

  1. stringコンテキストに s がありましたが、匿名オブジェクトを介して s を提供しintたため、何らかの形で mvc が混乱し、 を使用できませんでしroute1た。
  2. int匿名オブジェクトの をに変更したところ、コンテキスト内の と が匿名オブジェクトstringの URLと等しい URL が正しく生成され始めました。一方、他のすべてはそうではありませんでした。monthyear

つまり、匿名オブジェクトのプロパティはstring、リクエスト コンテキスト内のルート値の型と一致する型である必要があります。

しかし、このルールは必須ではないようです。 Test3 のようタイプを変更しました (上で確認できます)。それでも正しく動作します。MVC は、型自体を正しくキャストできます。


最後に、このすべての動作の説明を見つけました。以下の私の回答をご覧ください。

4

2 に答える 2

3

これは、私がそれを機能させるために使用する簡単な回避策です。

public static MvcHtmlString GetPeriodLink(this HtmlHelper html, 
                                          RequestContext context, 
                                          DateTime date)
{
    UrlHelper urlHelper = new UrlHelper(context);

    context.RouteData.Values["month"] = date.Month;
    context.RouteData.Values["year"] = date.Year;

    return MvcHtmlString.Create(
              urlHelper.Action(
                 (string)context.RouteData.Values["action"]));
}

monthとのyearエントリを から削除するだけcontext.RouteData.Valuesです。リクエスト コンテキストのエントリmonthとエントリを置き換えるだけです。yearそれらをコンテキストから削除すると (最初に行ったように)、この後に呼び出されるヘルパーのメソッドでは使用できなくなります。

このアプローチにより、私の拡張メソッドは、テスト 1で説明されているシナリオで機能します(質問を参照してください)。


最後に

Sanderson S.、Freeman A. - Pro ASP.NET MVC 3 Framework (3rd edition)を注意深く読み直すと、少なくともこのすべての説明が見つかりました。

パート 2 ASP.NET MVC の詳細

第 11 章 URL、ルーティング、およびエリア

発信 URL の生成

セクションでセグメント変数の再利用について:

ルーティング システムは、Html.ActionLink メソッドに指定されたどのパラメーターよりも URL パターンで前に出現するセグメント変数の値のみを再利用します。

month-yearセグメントが直後に一致し、とのcontroller値を指定している限り、後続のセグメント ( 、) はすべて再利用されません。匿名オブジェクトでそれらを指定しない限り、ルートには使用できないようです。したがって、route1 は一致しません。monthyearactionuser

本には警告さえあります:

この動作に対処する最善の方法は、それが起こらないようにすることです。この動作に依存せず、URL パターン内のすべてのセグメント変数に値を指定することを強くお勧めします。この動作に依存すると、コードが読みにくくなるだけでなく、ユーザーがリクエストを行う順序について推測することになり、アプリケーションがメンテナンスに入ったときに最終的に苦しむことになります。

まあ、それは私を噛んだ)))

覚えておくために 100 担当者を失う価値があります (ここでもう一度繰り返します) ルール:ルーティング システムは、指定されたパラメーターよりも URL パターンで前に発生するセグメント変数に対してのみ値を再利用します。

于 2012-10-30T04:31:35.260 に答える
-1

ユーザーのデフォルトのルートが 1 つあれば十分でしょうか? あれは

routes.MapRoute(
    "route1",
    "{controller}/{month}-{year}/{action}/{user}",
    new { user = "" }
);

それ以外の場合は、URL を作成するときに最初に route2 と一致しないように、ユーザー ルートに固有のものを許可する必要があります。

更新: ヘルパーを離れて Url.Action を直接操作することをお勧めします。上記のシナリオの 2 つのテストを次に示します。

 [TestFixture]
    public class RouteRegistrarBespokeTests
    {
        private UrlHelper _urlHelper;

        [SetUp]
        public void SetUp()
        {
            var routes = new RouteCollection();
            routes.Clear();
            var routeData = new RouteData();
            RegisterRoutesTo(routes);
            var requestContext = new RequestContext(HttpMocks.HttpContext(), 
                                           routeData);
            _urlHelper = new UrlHelper(requestContext, routes);
        }

        [Test]
        public void Should_be_able_to_map_sample_without_user()
        {
            var now = DateTime.Now;
            var result = _urlHelper.Action("Index", "Sample", 
                               new {  year = now.Year, month = now.Month });
            Assert.AreEqual(string.Format("/Sample/{0}-{1}/Index", 
                                      now.Month, now.Year ), result);
        }

        [Test]
        public void Should_be_able_to_map_sample_with_user()
        {
            var now = DateTime.Now;
            var result = _urlHelper.Action("Index", "Sample", 
                          new { user = "user1", year = now.Year, 
                                month = now.Month });
            Assert.AreEqual(string.Format("/Sample/{0}-{1}/Index/{2}", 
                                     now.Month, now.Year, "user1"), result);
        }



private static void RegisterRoutesTo(RouteCollection routes)
{
    routes.MapRoute("route1", "{controller}/{month}-{year}/{action}/{user}");
    routes.MapRoute("route2", "{controller}/{month}-{year}/{action}");
}

}
于 2012-10-31T12:01:24.523 に答える