URL ルーティング

作成者: Erik Reitan

Wingtip Toys サンプル プロジェクト (C#) のダウンロードまたは電子書籍 (PDF) のダウンロード

このチュートリアル シリーズでは、ASP.NET 4.5 と Microsoft Visual Studio Express 2013 for Web を使った ASP.NET Web Forms アプリケーション構築の基本について説明します。 Visual Studio 2013 の C# ソース コードを含むプロジェクトは、このチュートリアル シリーズと併せて使用できます。

このチュートリアルでは、Wingtip Toys サンプル アプリケーションを変更して、URL ルーティングをサポートするようにします。 ルーティングを使用すると、フレンドリで覚えやすく、検索エンジンによりサポートされた URL を Web アプリケーションで使用できるようになります。 このチュートリアルは、前のチュートリアル「メンバーシップと管理」に基づいており、Wingtip Toys チュートリアル シリーズの一部です。

ここでは、次の内容について学習します。

  • ASP.NET Web Forms アプリケーションのルートを登録する方法。
  • Web ページにルートを追加する方法。
  • ルートをサポートするためにデータベースからデータを選択する方法。

ASP.NET ルーティングの概要

URL ルーティングを使用すると、物理ファイルにマッピングされない要求 URL を受け入れるようにアプリケーションを構成できます。 要求 URL は、ユーザーがブラウザーに入力して Web サイト上のページを検索するための単なる URL です。 ルーティングを使用して、ユーザーにとって意味的にわかりやすく、検索エンジン最適化 (SEO) の役に立つ URL を定義します。

既定では、Web Forms テンプレートには ASP.NET Friendly URL が含まれています。 基本的なルーティング作業の多くは、Friendly URL を使用して実装されます。 ただし、このチュートリアルでは、カスタマイズされたルーティング機能を追加します。

URL ルーティングをカスタマイズする前は、Wingtip Toys サンプル アプリケーションは以下の URL を使用して製品にリンクします。

https://localhost:44300/ProductDetails.aspx?productID=2

URL ルーティングをカスタマイズすることで、Wingtip Toys サンプル アプリケーションは、読みやすい URL を使用して同じ製品にリンクするようになります。

https://localhost:44300/Product/Convertible%20Car

Routes

ルートとは、ハンドラーにマップされている URL のパターンです。 ハンドラーには、Web Forms アプリケーションの .aspx ファイルなどの物理ファイルを指定できます。 ハンドラーは、要求を処理するクラスにすることもできます。 ルートを定義するには、URL パターン、ハンドラー、および必要に応じてルートの名前を指定して、Route クラスのインスタンスを作成します。

アプリケーションにルートを追加するには、RouteTable クラスの静的 Routes プロパティに Route オブジェクトを追加します。 Routes プロパティは、アプリケーションのすべてのルートを格納する RouteCollection オブジェクトです。

URL パターン

URL パターンには、リテラル値と変数プレースホルダー (URL パラメーターと呼ばれます) を含めることができます。 リテラルとプレースホルダーは、スラッシュ (/) 文字で区切られた URL のセグメントに含まれます。

Web アプリケーションへの要求が行われると、URL がセグメントとプレースホルダーに解析され、変数値が要求ハンドラーに提供されます。 このプロセスは、クエリ文字列内のデータが解析され、要求ハンドラーに渡されるのと似ています。 どちらの場合も、変数情報は URL に含まれ、キーと値のペアの形式でハンドラーに渡されます。 クエリ文字列の場合、キーと値の両方が URL に含まれます。 ルートの場合、キーは URL パターンで定義されたプレースホルダー名であり、値のみが URL に含まれます。

URL パターンでは、プレースホルダーを中かっこ ( { および } ) で囲んで定義します。 1 つのセグメントに複数のプレースホルダーを定義できますが、プレースホルダーはリテラル値で区切られている必要があります。 たとえば、{language}-{country}/{action} は有効なルート パターンです。 しかし、{language}{country}/{action} は有効なパターンではありません。プレースホルダー間にリテラル値または区切り記号がないためです。 そのため、言語のプレースホルダーの値と、国のプレースホルダーの値を区切る場所がルーティングでは決定できません。

ルートのマッピングと登録

Wingtip Toys サンプル アプリケーションのページへのルートを含める前に、アプリケーションの起動時にルートを登録する必要があります。 ルートを登録するには、Application_Start イベント ハンドラーを変更します。

  1. Visual Studio のソリューション エクスプローラーで、Global.asax.cs ファイルを探して開きます。

  2. 以下のように、黄色で強調表示されているコードを Global.asax.cs ファイルに追加します。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Optimization;
    using System.Web.Routing;
    using System.Web.Security;
    using System.Web.SessionState;
    using System.Data.Entity;
    using WingtipToys.Models;
    using WingtipToys.Logic;
    
    namespace WingtipToys
    {
        public class Global : HttpApplication
        {
            void Application_Start(object sender, EventArgs e)
            {
              // Code that runs on application startup
              RouteConfig.RegisterRoutes(RouteTable.Routes);
              BundleConfig.RegisterBundles(BundleTable.Bundles);
    
              // Initialize the product database.
              Database.SetInitializer(new ProductDatabaseInitializer());
    
              // Create custom role and user.
              RoleActions roleActions = new RoleActions();
              roleActions.AddUserAndRole();
    
              // Add Routes.
              RegisterCustomRoutes(RouteTable.Routes);
            }
    
            void RegisterCustomRoutes(RouteCollection routes)
            {
              routes.MapPageRoute(
                  "ProductsByCategoryRoute",
                  "Category/{categoryName}",
                  "~/ProductList.aspx"
              );
              routes.MapPageRoute(
                  "ProductByNameRoute",
                  "Product/{productName}",
                  "~/ProductDetails.aspx"
              );
            }
        }
    }
    

Wingtip Toys サンプル アプリケーションが起動すると、Application_Start イベント ハンドラーが呼び出されます。 このイベント ハンドラーの最後に、RegisterCustomRoutes メソッドが呼び出されます。 この RegisterCustomRoutes メソッドは、RouteCollection オブジェクトの MapPageRoute メソッドを呼び出すことで各ルートを追加します。 ルートは、ルート名、ルート URL、および物理 URL を使用して定義されます。

最初のパラメーター ("ProductsByCategoryRoute") は、ルート名です。 必要なときにルートを呼び出すために使用されます。 2 番目のパラメーター ("Category/{categoryName}") は、コードに基づいて動的に変化するフレンドリな置換 URL を定義します。 このルートは、データに基づいて生成されるリンクをデータ コントロールに読み込む場合に使用します。 ルートは以下のように表示されます。

routes.MapPageRoute(
      "ProductsByCategoryRoute",
      "Category/{categoryName}",
      "~/ProductList.aspx"
  );

ルートの 2 番目のパラメーターには、中かっこ ({ }) で指定された動的な値が含まれています。 この場合、categoryName は適切なルーティング パスを決定するために使用される変数です。

Note

オプション

RegisterCustomRoutes メソッドを別のクラスに移動することで、コードの管理が容易になる場合があります。 Logic フォルダーで、別の RouteActions クラスを作成します。 上記 RegisterCustomRoutes のメソッドを、Global.asax.cs ファイルから新しい RoutesActions クラスに移動します。 RegisterCustomRoutes メソッドを Global.asax.cs ファイルから呼び出す方法の例として、RoleActions クラスと createAdmin メソッドを使用します。

Application_Start イベント ハンドラーの先頭にある RouteConfig オブジェクトを使用した RegisterRoutes メソッド呼び出しにも気付いたかもしれません。 この呼び出しは、既定のルーティングを実装するために行われます。 これは Visual Studio の Web Forms テンプレートを使用してアプリケーションを作成した際に、既定のコードとして含まれていました。

ルート データの取得と使用

前述のように、ルートは定義できます。 Global.asax.cs ファイル内の Application_Start イベント ハンドラーに追加したコードによって、定義可能なルートが読み込まれます。

ルートの設定

ルートにはコードの追加が必要です。 このチュートリアルでは、モデル バインドを使用して、データ コントロールのデータを使用してルートが生成されるときに使用される RouteValueDictionary オブジェクトを取得します。 RouteValueDictionary オブジェクトには、製品の特定のカテゴリに属する製品名のリストが含まれます。 データとルートに基づき、製品ごとにリンクが作成されます。

カテゴリと製品のルートを有効にする

次に、ProductsByCategoryRoute を使用して各製品カテゴリのリンクに含める正しいルートを決定するために、アプリケーションを更新します。 さらに、各製品のルーティング リンクを含めるために、ProductList.aspx ページも更新します。 リンクは変更前と同じように表示されますが、各リンクでは URL ルーティングが使用されるようになります。

  1. Site.Master ページがまだ開いていない場合は、ソリューション エクスプローラーでそれを開きます。

  2. "categoryList" という名前の ListView コントロールを、黄色で強調表示された変更内容で更新し、マークアップが以下のようになるようにします。

    <asp:ListView ID="categoryList"  
        ItemType="WingtipToys.Models.Category" 
        runat="server"
        SelectMethod="GetCategories" >
        <ItemTemplate>
            <b style="font-size: large; font-style: normal">
            <a href="<%#: GetRouteUrl("ProductsByCategoryRoute", new {categoryName = Item.CategoryName}) %>">
                <%#: Item.CategoryName %>
            </a>
            </b>
        </ItemTemplate>
        <ItemSeparatorTemplate>  |  </ItemSeparatorTemplate>
    </asp:ListView>
    
  3. ソリューション エクスプローラーProductList.aspx ページを開きます。

  4. ProductList.aspx ページの ItemTemplate 要素を、黄色で強調表示された更新内容で更新し、マークアップが以下のようになるようにします。

    <ItemTemplate>
      <td runat="server">
        <table>
          <tr>
            <td>
              <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>">
                <image src='/Catalog/Images/Thumbs/<%#:Item.ImagePath%>'
                  width="100" height="75" border="1" />
              </a>
            </td>
          </tr>
          <tr>
            <td>
              <a href="<%#: GetRouteUrl("ProductByNameRoute", new {productName = Item.ProductName}) %>">
                <%#:Item.ProductName%>
              </a>
              <br />
              <span>
                <b>Price: </b><%#:String.Format("{0:c}", Item.UnitPrice)%>
              </span>
              <br />
              <a href="/AddToCart.aspx?productID=<%#:Item.ProductID %>">
                <span class="ProductListItem">
                  <b>Add To Cart<b>
                </span>
              </a>
            </td>
          </tr>
          <tr>
            <td>&nbsp;</td>
          </tr>
        </table>
        </p>
      </td>
    </ItemTemplate>
    
  5. ProductList.aspx.cs のコードビハインドを開き、黄色で強調表示された以下の名前空間を追加します。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using WingtipToys.Models;
    using System.Web.ModelBinding;
    using System.Web.Routing;
    
  6. コードビハインド (ProductList.aspx.cs) の GetProducts メソッドを、以下のコードで置き換えます。

    public IQueryable<Product> GetProducts(
        [QueryString("id")] int? categoryId,
        [RouteData] string categoryName)
    {
        var _db = new WingtipToys.Models.ProductContext();
        IQueryable<Product> query = _db.Products;
    
        if (categoryId.HasValue && categoryId > 0)
        {
            query = query.Where(p => p.CategoryID == categoryId);
        }
    
        if (!String.IsNullOrEmpty(categoryName))
        {
            query = query.Where(p =>
                String.Compare(p.Category.CategoryName,
                categoryName) == 0);
        }
        return query;
    }
    

製品の詳細のコードを追加する

次に、ProductDetails.aspx ページのコードビハインド (ProductDetails.aspx.cs) を更新し、ルートデータが使用されるようにします。 新しい GetProduct メソッドは、ユーザーが古い、フレンドリでない非ルーティング URL を使用したリンクをブックマークしている場合に備えて、クエリ文字列値も受け入れることに注意してください。

  1. GetProduct メソッドのコードビハインド (ProductDetails.aspx.cs) を、以下のコードに置き換えます。

    public IQueryable<Product> GetProduct(
            [QueryString("ProductID")] int? productId,
            [RouteData] string productName)
    {
        var _db = new WingtipToys.Models.ProductContext();
        IQueryable<Product> query = _db.Products;
        if (productId.HasValue && productId > 0)
        {
            query = query.Where(p => p.ProductID == productId);
        }
        else if (!String.IsNullOrEmpty(productName))
        {
            query = query.Where(p =>
                  String.Compare(p.ProductName, productName) == 0);
        }
        else
        {
            query = null;
        }
        return query;
    }
    

アプリケーションの実行

ここで、アプリケーションを実行して、更新されたルートを確認できます。

  1. F5 キーを押して、Wingtip Toys サンプル アプリケーションを実行します。
    ブラウザーが開き、Default.aspx ページが表示されます。
  2. ページ上部の [製品] リンクをクリックします。
    すべての製品が ProductList.aspx ページに表示されます。 以下のURL (お使いのポート番号に基づく) がブラウザーに表示されます。
    https://localhost:44300/ProductList
  3. 次に、ページ上部付近にある [車] カテゴリのリンクをクリックします。
    ProductList.aspx ページには、車のみが表示されます。 以下のURL (お使いのポート番号に基づく) がブラウザーに表示されます。
    https://localhost:44300/Category/Cars
  4. ページ上で 1 番最初に表示されている車 ("オープン カー") の名前を含むリンクをクリックして、製品の詳細を表示します。
    以下のURL (お使いのポート番号に基づく) がブラウザーに表示されます。
    https://localhost:44300/Product/Convertible%20Car
  5. 次に、以下のルーティングされていない URL (お使いのポート番号に基づく) をブラウザーに入力します。
    https://localhost:44300/ProductDetails.aspx?productID=2
    ユーザーがリンクをブックマークしている場合に備えて、コードはクエリ文字列を含む URL も認識します。

まとめ

このチュートリアルでは、カテゴリと製品にルートを追加しました。 モデル バインドを使うデータ コントロールにルートを統合する方法を学習しました。 次のチュートリアルでは、グローバル エラー処理を実装します。

その他のリソース

ASP.NET Friendly URL
メンバーシップ、OAuth、SQL Database を使用したセキュリティで保護された ASP.NET Web Forms アプリを Azure App Service に配置する
Microsoft Azure - 無料試用版