WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

DropDownList への SelectList の渡し方

by WebSurfer 25. February 2019 14:37

ASP.NET MVC5 の Html ヘルパーの DropDownList および DropDownListFor に表示するデータを、アクションメソッドからビューにどのように渡すかということについて書きます。

DropDownList

上の画像は、先の記事「スキャフォールディング機能」で書いた通りにスキャフォールディング機能を使って自動生成させたコードで、その中の Edit 画面を表示したものです。

SupplierID と CategoryID が Html ヘルパーの DropDownList を使ってドロップダウン形式で表示されるようになっています。上の画像は CategoryID を展開したところで、CategoryName の一覧が表示されています。

スキャフォールディング機能で自動生成されたコードが基本になるでしょうから、それがどうなっているかを書きます。

まずアクションメソッド Edit で SelectList オブジェクトを生成し ViewBag に設定しています。以下のコードの通りです。

public ActionResult Edit(int? id)
{
  NORTHWINDEntities db = new NORTHWINDEntities();
  Products products = db.Products.Find(id);

  ViewBag.CategoryID = 
    new SelectList(db.Categories, "CategoryID", 
                   "CategoryName", products.CategoryID);

  ViewBag.SupplierID = 
    new SelectList(db.Suppliers, "SupplierID", 
                   "CompanyName", products.SupplierID);

  return View(products);
}

コンストラクタに SelectList(IEnumerable, String, String, Object) を使って、第 4 引数に selectedValue を設定しているところに注目してください。これによりビューの DropDownList が html に変換された際、select 要素内の当該 option 要素に selected 属性が付与されます。

ビューの DropDownList のコードは以下のようになります。第 1 引数がアクションメソッドで設定した ViewBag のキー名、第 2 引数が null になっているところに注目してください。

@Html.DropDownList("SupplierID", null, 
        htmlAttributes: new { @class = "form-control" })

@Html.DropDownList("CategoryID", null, 
        htmlAttributes: new { @class = "form-control" })

DropDownList の第 2 引数が null となっていますが、第 2 引数の設定に関わらず ViewData / ViewBag から型が IEnumerable<SelectListItem> でキー名が第 1 引数と同じものを探してきます。

(例えば、上記のアクションメソッドで ViewBag.SupplierID の設定を削除すると、ビューの DropDownList のコードで「キー 'SupplierID' を持つ ViewData 項目の型は 'System.Int32' ですが、'IEnumerable<SelectListItem>' でなければなりません」というエラーになります)

DropDownList の第 1 引数を元に ViewBag で渡されたデータ(アクションメソッドで設定された SelectList オブジェクト)を取得するので、上の画像の通りドロップダウン形式で表示できるようになります。さらに、SelectList コンストラクタの第 4 引数に設定した selectedValue によって当該 option 要素に selected 属性が設定された結果が表示されます。

なお、DropDownList の第 2 引数を (SelectList)ViewBag.Supplier としたりすると、SelectList のコンストラクタで第 4 引数に設定した selectedValue が無視されるので注意してください。理由は不明です。

ViewData / ViewBag に DropDownList の第 1 引数と同じキー名がない場合は、DropDownList の第 2 引数の設定が有効になるようです。例えば、アクションメソッドで ViewBag.SupplierID を ViewBag.Supplier に変更した場合、DropDownList("SupplierID", (SelectList)ViewBag.Supplier, ...) として selected の設定を含めて期待した結果が得られます。

ViewData / ViewBag を探す順序ですが、検証してみると、まず最初に ViewData を、それになければ ViewBag を探すという結果になりました。ViewData / ViewBag に同じキー名があると、ViewData のデータが使われます。その際、もし ViewData のデータが不正ですと(IEnumerable<SelectListItem> 型でないと)エラーになります。

以上は DropDownListFor を使っても同様です。第 1 引数は model => model.SupplierID のようになりますが、プロパティ名 SupplierID から ViewData / ViewBag を探して設定してくれます。

Tags: , ,

MVC

日付と通貨の書式設定

by WebSurfer 4. February 2019 18:10

ASP.NET MVC5 アプリで日付(DateTime 型)と通貨(Decimal 型)を表示し、編集してデータベースを更新する場合、どのように書式設定を行うのが良いかということについて書きます。

日付と通貨の書式設定

上の画像は DisplayFor と TextBoxFor を上下に並べて表示していますが、その「契約日」と「価格」に示したように、

  1. DisplayFor 等を使った表示の場合のみ書式設定を行い、
  2. テキストボックスに値を表示する場合は書式設定しない。または、"{0:F0}" のような書式設定に留めパースできない文字は含めないようにする。

・・・のがよさそうです。

スキャフォールディング機能を使って CRUD 可能なアプリを作ると Index (レコード一覧), Create, Delete, Edit, Details というアクションメソッドとそれに対応するビューが生成されますが、Index, Delete, Details を上記 (1) で、Create, Edit を上記 (2) で表示するということです。

テキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のように表示することはできますが、クライアント側での検証を無効にしてサーバーに POST したとしても、DateTime 型や Decimal 型にパースできないので結局モデルバインディングの際の検証が通りません。

どうしてもテキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のようにしたいということであれば、モデルのプロパティも含めて全て String 型として扱い、検証は正規表現のみを使って行うということも考えられますが、データベースの型が datetime とか money でしょうから、サーバー側での取り扱いを考えると現実的ではなさそうです。

それに、ユーザーとしても、編集する際にいちいち ¥ とか , を入力するのは煩わしいはずで、喜んではくれないのでは?

上の画像のように表示するための方法ですが、DisplayFor での表示の書式はモデルのプロパティに DisplayFormatAttribute を付与して設定し、テキストボックスには TextBoxFor を使ってその第 2 引数に "{0:yyyy/MM/dd}"、"{0:F0}" などの書式を設定するのがよさそうです。

EditorFor を使うと書式が自由に設定できないし(DisplayFormatAttribute の設定と同じでよければ可能ですが)、レンダリングされる html ソースの input 要素の type 属性が問題になることがありますので使う場合は要注意です。

具体的には以下のようにします。

Model

public class PurchaseRecord
{
  [Display(Name = "ID")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  public int ID { get; set; }

  [Display(Name = "契約日")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(
      @"^\d{4}/\d{2}/\d{2}( \d{1,2}:\d{2}:\d{2})?$", 
      ErrorMessage = "yyyy/MM/dd 形式")]
  [DisplayFormat(DataFormatString = "{0:yyyy年M月d日}")]
  public DateTime ContractDate { get; set; }

  [Display(Name = "価格")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  [DisplayFormat(DataFormatString = "{0:C0}")]
  public decimal Price { get; set; }
}

View(一部の抜粋)

<div class="form-group">
  @Html.LabelFor(model => model.ID, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ID)
    @Html.EditorFor(model => model.ID, 
      new { htmlAttributes = new { @class = "form-control" } })
    @Html.ValidationMessageFor(model => model.ID, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.ContractDate, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ContractDate)
    @Html.TextBoxFor(model => model.ContractDate, 
      "{0:yyyy/MM/dd}", new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.ContractDate, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.Price, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.Price)
    @Html.TextBoxFor(model => model.Price, 
      "{0:F0}",new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.Price, 
      "", new { @class = "text-danger" })
  </div>
</div>

あと、オマケの話ですが、ASP.NET Web Forms アプリで使う GridView などで書式設定する際も同様なことはできます。以下の画像を見てください。

GridView で日付と通貨の書式設定

赤枠で囲った部分は[編集]ボタンをクリックして編集モードにしたテキストボックスですが、他の行との違いを見てください。

Tags: , ,

MVC

MVC5 で AjaxHelper が働かない

by WebSurfer 28. May 2018 15:40

Visual Studio Community 2015(以下、VS2015 と書きます)のテンプレートで [MVC] を選択して生成した ASP.NET MVC5 アプリケーションでは、そのままでは AjaxHelper(Ajax.BeginForm, Ajax.ActionLink など)が働きません。その理由と解決策を書きます。

Ajax.ActionLink を利用したページ

上の画像は Ajax.ActionLink を使って a 要素の href 属性に部分ビューの url を設定したハイパーリンクをレンダリングし(青文字の部分)、ユーザーがハイパーリンクをクリックすると Ajax を利用して部分ビューを呼び出し、応答を UpdateTargetId に指定した div 要素内に書き出した結果です(Customer Details 以下の部分)。

上の画像のアプリは対策済みなので、Ajax を利用した部分ビューの呼び出しと、UpdateTargetId に指定した div 要素内へ応答の書き出しは成功しています。

未対策の場合はハイパーリンクは普通の a 要素の機能しかないので、クリックすると部分ビューは同期呼び出しされ、ブラウザの現在のウィンドウ全体が部分ビューに書き換えられて表示されます。

なぜそうなるかと言うと、必要な外部 .js ファイルがダウンロードされる設定がされてないからです。

そんな設定は自分でやるのが当たり前と言われるかもしれませんが、Visual Studio 2010 Professional(以下、VS2010 と書きます)のテンプレートで自動生成される MVC4 アプリのプロジェクトでは、AjaxHelper に必要な外部 .js ファイルがダウンロードされる設定が含まれているのです。

なので、VS2015 で MVC5 アプリのプロジェクトを作って、既存の MVC4 の AjaxHelper を使うコードを MVC5 に移植しただけでは期待した Ajax の機能は働きません。

何を隠そう、自分はその理由が分からずハマってしまいました。(汗) また無駄な時間を費やすことがないよう、以下に備忘録として原因と対策を書いておきます。

MVC4 以降は控えめな JavaScript を利用して、Ajax.BeginForm, Ajax.ActionLink などから生成される form 要素、a 要素の属性の設定に応じて Ajax 呼び出しと応答の表示を行うようになっています。

その控えめな JavaScript は jquery.unobtrusive-ajax.js または jquery.unobtrusive-ajax.min.js というファイルに含まれています。(ちなみに、MVC3 以前は「控えめ」ではない MicrosoftAjax.js と MicrosoftMvcAjax.js を使います)

例えば、Ajax.ActionLink を使った場合、上の画像の例の「Ms. Rosmarie Carroll」のハイパーリンクの html 要素は以下のようになります。

<a  data-ajax="true" 
    data-ajax-mode="replace" 
    data-ajax-update="#results" 
    href="/Customer/Details/6">
    Ms. Rosmarie Carroll
</a>

控えめな JavaScript は、上の a 要素の属性 data-ajax, data-ajax-mode, data-ajax-update の設定に従って Ajax 呼び出しと応答の処理を行います。

従って、やるべきことは控えめな JavaScript の外部ファイルがダウンロードされるように設定を行うことです。VS2010 で作る MVC4 アプリはそのあたりの設定が自動的に行われますが、VS2015 で作る MVC5 アプリは開発者が自分で設定しなければなりません。

前置きが長くなりましたが、その方法を以下に書きます。

まず、NuGet から Microsoft.jQuery.Unobtrusive.Ajax をインストールします。以下の画像を見てください。

NuGet からインストール

インストールが完了すると、Script フォルダに jquery.unobtrusive-ajax.js, jquery.unobtrusive-ajax.min.js がインストールされるはずですのでチェックしてください。

スクリプトファイル

App_Start フォルダの BundleConfig.cs ファイルを開いて上記 .js ファイルがバンドルされるように設定します。下の画像の赤枠部分を見てください。ここでは MVC4 と同様に、クライアント側での検証用のスクリプトファイルと一緒にダウンロードされるようにしています。

BundleConfig.cs の設定

_Layout.cshtml には @RenderSection("scripts", required: false) が含まれているので、_Layout.cshtml を使う View では以下のコードを追加すれば OK です。

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

_Layout.cshtml を使わない View の場合は @Scripts.Render("~/bundles/jqueryval") の位置が jQuery.js の後に来るように注意してください。

Tags:

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2021  >>
MoTuWeThFrSaSu
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

View posts in large calendar