yamamoWorks

.NET技術を中心に気まぐれに更新していきます

DateTime型のルートパラメータを省略したい場合

例えばカレンダーWebアプリを作るとして、指定した日の予定を表示するURLを以下のようにしたいとします。さらに日付の部分が省略された場合は当日の日付が指定されたものとして処理したいとします。

/calendar/event/2009-10-14
/calendar/event/
  ⇒  当日として処理したい

こういう要件の場合、ルート定義で日付部分の規定値をどう書くか少し悩みます。

routes.MapRoute(
"Default",
"{controller}/{action}/{dateTime}",
new { controller = "calendar", action = "event", dateTime = /???/ }
);

public ActionResult Event(DateTime dateTime)
{
return View();
}

「dateTime = DateTime.Now.Date」とやってみたくなりますが、これはNGです。Global.asaxのMvcApplication.RegisterRoutes()はアプリケーション起動時(Webアプリへの最初のアクセス時)に実行されるので、その時点での日付が規定値となってしまいます。

「dateTime = string.Empty」と定義して日付を文字列として扱ってしまう方法もありますが、Actionメソッド側で「空文字だったら当日の日付にする」という処理を行うのはスマートじゃありません。

そこで、IModelBinderを利用してみます。


IModelBinderは本来(?)クライアントからPOSTされたデータからモデルのインスタンスを生成してActionメソッドに渡す為の仕組みのようですが、今回は「空文字だったら当日の日付にする」処理をやらせます。

まず、ルート定義では「dateTime = string.Empty」としておきます。("Now"とか任意の文字でもOK)

次に、DefaultModelBinderを継承したクラスでBindModelメソッドをオーバーライドし、「空文字(もしくは任意の文字)だったら当日の日付を返す」ようにします。

public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider[bindingContext.ModelName];

if (value != null & string.IsNullOrEmpty((string)value.RawValue))
{
return DateTime.Now.Date;
}

return base.BindModel(controllerContext, bindingContext);
}
}

そしてGlobal.asaxでこのクラスをDateTime型のModelBinderとして登録します。

public static void RegisterRoutes(RouteCollection routes)
{
// 型に対応するModelBinderの登録
ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default",
"{controller}/{action}/{dateTime}",
new { controller = "calendar", action = "event", dateTime = string.Empty }
);
}

これで{dateTime}が省略された場合に当日の日付がActionメソッドに渡るようになります。

※ルート定義とActionメソッドの引数の型が一致しなくなるのが気になる場合は「dateTime = DateTime.MinValue」と定義して、DateTimeModelBinderで 「DateTime.MinValueだったら当日の日付を返す」としてもいいかも。

【追記】

Null許容型を使えばもっと簡単にできますね。

routes.MapRoute(
"Default",
"{controller}/{action}/{dateTime}",
new { controller = "calendar", action = "event", dateTime = (DateTime?)null }
);

public ActionResult Event(DateTime? dateTime)
{
return View(dateTime ?? DateTime.Now.Date);
}