ASP.NET MVC 日付でルーティング
by 深夜連絡 ASP.NET MVC な Web アプリ Advent Calendar 2013 9 日目
今回は、日付を含む URL のルーティング方法について書きます。
例として、「~/MyDiary/2013/03/25」という URL でアクセスした場合、次のようにコントローラー:DiaryController、メソッド:Details にマッピングして、
public class DiaryController : Controller { [HttpGet] public ActionResult Details(DateTime date); }
次のようなビューを表示するサンプルを作成します。
実装方法は、大きく分けて次の 3 つに分けられます。
- 普通にマッピング
- 制約をつけてマッピング IRouteConstraint
- 属性で制約をつけてマッピング
(※最後の方法は ASP.NET MVC 5 以前では使用できません。)
準備
プロジェクトの用意
Visual Studio のサイト から、Visual Studio Express 2013 for Web(以下、VS Express 2013)をインストールします。無償です。
VS Express 2013 を開き、メニューから ファイル>新規作成>プロジェクト を選択します。
「ASP.NET Web アプリケーション」を選択し、OK します。
コントローラーの用意
DiaryController クラスを用意します。
DiaryController.cs :
public class DiaryController : Controller { [HttpGet] public ActionResult Details(DateTime date) { ViewBag.Date = date.ToLongDateString(); return View(); } }
ビューの用意
Viewsフォルダ>Diaryフォルダに、Details.cshtml を用意します。
Details.cshtml :
@{ ViewBag.Title = "Diary"; }
<h2>Diary @ViewBag.Date</h2>
<p>
ワレワレは宇宙人ダ
</p>
以上でコントローラーとビューが用意できたので、マッピングを行います。
ルーティングの設定
3つの方法があります。
普通にマッピング
App_Start フォルダ内にある、RouteConfig.cs にて以下のようにルーティングを追加します。
using System.Web.Mvc; using System.Web.Routing; namespace WebApplication13 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // ↓今回追加 routes.MapRoute( name: "DiaryRoute", url: "MyDiary/{*date}", defaults: new { controller = "Diary", action = "Details" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
URL「MyDiary/{*date}」date の前の * は、この場合”ここから末尾までが date の値だよ、スラッシュが含まれていても date の一部だよ”と示しています。* をつけないと、「MyDiary/2013/03/25」にマッチしません。スラッシュは、URL を判別するための基準だからです。
ちなみに「MyDiary/2013-03-25」でもマッピングされます。
注意したいのは、このルーティング設定だと「MyDiary/2013/08/32」といったあり得ない日付を指定した場合、下のようなエラーがでることです。
このエラーを回避し、ありえない日付が指定された場合は 404 Not Found を返すようにするには、ルーティングの制約を設定する必要があります。後述の2つの方法では、これを実装しています。
制約をつけてマッピング IRouteConstraint
変な日付の場合は、404 Not Found を返すようにします。
先ほどの RouteConfig.cs で追加したルーティング設定にて、以下のように constraints 引数を定義します。
(この方法は、ASP.NET MVC 5 以前では使えません。)
//using System.Web.Mvc.Routing.Constraints; routes.MapRoute( name: "DiaryRoute", url: "MyDiary/{*date}", defaults: new { controller = "Diary", action = "Details" }, constraints: new { date = new DateTimeRouteConstraint() } );
DateTimeRouteconstraint クラス(コードに using System.Web.Mvc.Routing.Constraints の追加が必要です。)は、IRouteConstraints の実装クラスで、パラメーターが DateTime 型であるかどうかをチェックしています。
もし、ASP.NET MVC 5 より前を利用する場合は、自分で IRouteConstraints 実装クラスを用意し、同様に constraints 引数にて設定する必要があります。後述の参考サイトにも実装例がありますが、DateTimeRouteConstraint.cs を参照して書いてみました。
using System; using System.Globalization; using System.Web; using System.Web.Routing; public class DateTimeRouteConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (parameterName == null) { throw new ArgumentNullException("parameterName"); } if (values == null) { throw new ArgumentNullException("values"); } object value; if (values.TryGetValue(parameterName, out value) && value != null) { if (value is DateTime) { return true; } DateTime result; string valueString = Convert.ToString(value, CultureInfo.InvariantCulture); return DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out result); } return false; } }
日付制約の IRouteConstraint 実装例や、ルーティングの制約については、@IT [ASP.NET MVC]ルート・パラメータに妥当な日付が渡されているかを判定するには?[3.5、4以降、C#、VB] が参考になります。
ASP.NET MVC ではこのように、パラメータの制約を自分で実装することができます。
属性で制約をつけてマッピング
ASP.NET MVC 5 から追加された属性ルーティングを利用すれば、設定も制約も、とても少ないコードで実現できます。(ASP.NET MVC 5 で可能です。)
App_Startフォルダ内の RouteConfig.cs にて、↓のように routes.MapMvcAttributeRoutes(); を記述します。
using System.Web.Mvc; using System.Web.Routing; namespace WebApplication13 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // ↓追加 routes.MapMvcAttributeRoutes(); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
DiaryController にて、↓のようにメソッドの上に Route 属性を付与します。
public class DiaryController : Controller { [HttpGet] [Route("MyDiary/{*date:datetime}")] public ActionResult Details(DateTime date) { ViewBag.Date = date.ToLongDateString(); return View(); } }
属性ルーティングは、制約をシンプルに記述できるのが良いです。