miso_soup3 Blog

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

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);
}

次のようなビューを表示するサンプルを作成します。

f:id:miso_soup3:20131214065543p:plain

実装方法は、大きく分けて次の 3 つに分けられます。

  • 普通にマッピング
  • 制約をつけてマッピング IRouteConstraint
  • 属性で制約をつけてマッピング

(※最後の方法は ASP.NET MVC 5 以前では使用できません。)

準備

プロジェクトの用意

Visual Studio のサイト から、Visual Studio Express 2013 for Web(以下、VS Express 2013)をインストールします。無償です。

VS Express 2013 を開き、メニューから ファイル>新規作成>プロジェクト を選択します。
「ASP.NET Web アプリケーション」を選択し、OK します。

「MVC」を選択し 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();
	}
}

属性ルーティングは、制約をシンプルに記述できるのが良いです。