読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC で Twitter Bootstrap を使ってみた (3)

前回ではフォームのスタイルとスキャフォールディングを弄って、デフォルト時の見た目は非常にかっこよくなりました。

しかし、実際に submit してみるとエラー表示が結構残念な感じです。当然ながら Bootstrap のクラスを使っていないからですね。

ちなみに MVC 3 ではエラーメッセージはクライアントサイドで表示されるものと、サーバーサイドで出力されるものが存在しています。クライアントサイドに関しては標準で使用している jquery.validate.unobtrusive.js が MVC 3 のデフォルト値にべったりなので、とりあえず今回は無効にしておきます。

余談ですが、クライアントサイド検証を無効にするには Web.config か HtmlHelper で設定するだけです。

<appSettings>
  <add key="webpages:Version" value="1.0.0.0"/>
  <add key="ClientValidationEnabled" value="false"/>
  <add key="UnobtrusiveJavaScriptEnabled" value="false"/>
</appSettings>
@{
    Html.EnableClientValidation(false);
    Html.EnableUnobtrusiveJavaScript(false);
}

これでクライアントサイド検証は動きません。それでは本題に戻って、まずは Bootstrap ではエラー時にどのようなクラスが指定する必要があるか調べます。これは公式ページにサンプルがあるので安心です。

<div class="clearfix error">
  <label for="errorInput">Input with error</label>
  <div class="input">
    <input class="xlarge error" id="errorInput" name="errorInput" size="30" type="text" />
    <span class="help-inline">Small snippet of help text</span>
  </div>
</div><!-- /clearfix -->

重要なのは clearfix と同時に指定されている error とメッセージの span に指定されている help-inline です。この 2 つのクラスをエラー時に出力してあげれば ASP.NET MVC でもかっこいいエラー表示が出来るわけです。

ちなみに見た目は以下のような感じです。やっぱりかっこいいですね。

それでは ASP.NET MVC のエラー周りについて復習です。実際にエラーメッセージを出力しているのは、スキャフォールディングで生成したテンプレートに書かれている ValidationMessageFor というメソッドです。

@Html.ValidationMessageFor(model => model.Name)

そしてこのメソッドが結構残念というか、まあ仕方ないんですが出力される HTML を全くカスタマイズ出来ないという残念な仕様となってます。ちなみにエラーの場合には以下のような HTML が出力されるようになっています。

<span class="field-validation-error">Name フィールドが必要です。</span>

しかし今回は class 属性だけが必要なので、HTML 属性を指定できる ValidationMessageFor のオーバーロードメソッドを使うことにします。

以下のように匿名クラスで class の値を指定するだけです。今回の場合は help-inline を指定しました。ちなみに @class と書いている理由は class だと予約語と衝突してしまうからです。@ を頭に付けると予約語でも使えるようになります。

@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })

これでエラーメッセージに関しては対応できました。残るは clearfix と同時に指定されている error ですが、これは割と厄介な問題です。

何故厄介だというと、input やエラーメッセージの親に error クラスを付けないといけないからです。単純に考えればエラー時に if 文で出し分けすればいい話なんですが、エラーが発生したかどうかを簡単に bool 値として知る方法は用意されていません。

ということなので HTML ヘルパーを拡張することにしましょう。今回、単純に指定されたプロパティにエラーが発生しているかを bool で返す IsValid メソッドを作成しました。

public static bool IsValid<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
    var name = ExpressionHelper.GetExpressionText(expression);
    var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

    ModelState modelState;

    return !htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState) || modelState.Errors.Count <= 0;
}

ラムダ式からプロパティ名を取得し、HTML のフィールド名に変換してから ModelState にエラーが登録されていないか確認して bool 値として返しています。

それではビューにクラスを切り替えるコードを追加しましょう。

<div class="@(Html.IsValid(model => model.Name) ? "clearfix" : "clearfix error")">

全く美しくないですが、まあ一応 error の出し分けは出来るので目を瞑ります。それでは実際に動作させてみました。

Bootstrap のデモと同じようにエラー時のスタイルが当たっていることが確認できました。

やっぱり error の出し分けがあまりにも後ろ向きな対応だったので、次回で改善してみようと思います。