喜帳面の日記

50歳越えおやじのASP.NET MVC への挑戦日記です。

Visual Studio Express 2012 for Web でいってみる 16.ドロップダウンリストとPartialView

今回は、ドロップダウンリストとPartialViewにチャレンジしたときの記録です。
 初心者が行き当たりばったりにいろいろ動かしてみたときのメモなので記載内容には勝手な解釈や誤りもあるかと思います。そのあたりのご指摘よろしくお願いします。
やりたかったことは、
『一覧表示に絞り込み条件を付けて一覧部分をPartialViewで表示する』です。
出来上がりの画面はこんな感じになりました。

f:id:SannomiyaNotes:20121220192741p:plain

 商品区分をドロップダウンリストから指定
表示ボタンのクリックで指定の商品区分を持つ商品を一覧表示します。
 商品区分のドロップダウンリストはdbよりデータを持ってきますが、先頭に「すべての商品区分」という行を追加しています。
商品の一覧部分は、部分ビューですが、Ajax.BeginFormを使用しています。
 
参考にした情報は

SQL Server 2008 R2 サンプルで学ぶ ASP.NET MVC アプリケーション開発』

http://msdn.microsoft.com/ja-jp/data/ff723829.aspx

MVC2と若干古いのですがいろいろ参考になりました。

尚、このページのサンプルのZIPファイル内のサンプルデータベース「NorthwindJ」を今回もサンプルとして使用しています。

あとは、以下のブログ

ソースコードから理解する技術-UnderSourceCode
ASP.NET MVC 3 Razor - Ajax.BeginForm
非常に参考になりました。
私のところの環境は以下の通りです。

Windows7

SQL Server 2012 Express Edition (64-bit)

Visual Studio Express 2012 for Web

チャレンジの手順は以下の通りです。


 1.新しいプロジェクトを作成
テンプレートはインターネットアプリケーション
 
2.[Models]フォルダに[NorthwindJ.edmx]を作成
[Models]フォルダで右クリック、[追加]ー[新しい項目]で
[新しい項目の追加ダイアログ]を表示し、[Web]ー[データ]ー[ADO.NETEntity Data Model]を選択し[NorthwindJ]のedmxを作成。今回はテーブルより[商品区分]と[商品]を指定。
 

f:id:SannomiyaNotes:20121220193036p:plain

 

3.[Models]フォルダにビュー表示用のクラス[商品一覧ViewModel.cs]を作成

スクリプトはこんな感じです。3つのクラスを用意しました。

・public class 商品区分sViewModel:ドロップダウンリスト用Modelを作成

・public class VM_商品:商品一覧表示用のクラス

・public class 商品sViewModel:商品一覧用のModelを作成

<public class 商品区分sViewModel>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

//以下のNameSpaceを追加
using System.Web.Mvc;
using System.ComponentModel;

namespace Mvc4ApplicationE.Models
{
    public class 商品区分sViewModel
    {
        private NorthwindJEntities db = new NorthwindJEntities();
        //コンストラクターでデータを取得して商品区分一覧リストを作成
        public 商品区分sViewModel()
        {
            // --- 選択可能な商品区分リストの生成 ---
            var 商品区分SelectItemList = new List() 
            { 
                // "すべて"を表す商品区分をリストの先頭に追加
                new SelectListItem 
                { 
                    Value = "0",    // "すべて"を表す商品区分コードには "0" を使用
                    Text = "すべての商品区分" 
                } 
            };
            foreach (var c in 
                     from c in this.db.商品区分 
                     orderby c.区分コード 
                     select c)
            {
                var listItem = new SelectListItem
                {
                    Value = c.区分コード.ToString(),
                    Text = c.区分名
                };
                商品区分SelectItemList.Add(listItem);
            }
            商品区分s = 商品区分SelectItemList;
        }
        // (DropDownListに適用可能な) 商品区分一覧リスト
        public IEnumerable 商品区分s { get; private set; }
    }

 <public class VM_商品>

    /// 
    /// 商品の一覧用
    /// 
    public class VM_商品
    {
        [DisplayName("コード")]
        public int 商品コード { get; set; }

        [DisplayName("品名")]
        public string 商品名 { get; set; }

        [DisplayName("区分")]
        public int 区分コード { get; set; }
    }

 <public class 商品sViewModel>

public class 商品sViewModel  
    public class 商品sViewModel  
    {
        private NorthwindJEntities db = new NorthwindJEntities();
        //コンストラクターでデータをセット
        public 商品sViewModel(int 区分コード)
        {
            int 区分コードMIN = 区分コード;
            int 区分コードMAX = 区分コード;
            if (区分コード == 0) //全ての場合、MINとMAXをデータから取得する。
            {
                区分コードMIN = (from t in db.商品区分 select t.区分コード).Min();
                区分コードMAX = (from t in db.商品区分 select t.区分コード).Max();
            }
            IList<VM_商品> 商品SelectItemList = new List<VM_商品>();
            foreach (var s in
                     from s in this.db.商品
                     where s.区分コード >= 区分コードMIN && s.区分コード <= 区分コードMAX 
                     orderby s.商品コード
                     select s)
            {
                var item = new VM_商品();
                item.商品コード = s.商品コード;
                item.商品名 = s.商品名;
                item.区分コード = (int)s.区分コード;
                商品SelectItemList.Add(item);
            }
            商品s = 商品SelectItemList;
        }
        public IList<VM_商品> 商品s { get; private set; }
    }
}

4.[Controllers]フォルダにコントローラー[商品Controller.cs]を作成

 [Controllers]フォルダを右クリック、[追加]ー[コントローラー]で[コントローラーの追加]ダイアログを表示して、今回はスキャフォールディングのテンプレート指定は[空のMVCコントローラー]を指定して作成しました。ので、ほぼ手作りですね。こんな感じのスクリプトになりました。

namespace Mvc4ApplicationE.Controllers
{
    public class 商品Controller : Controller
    {
        // 商品一覧の抽出条件指定
        public ActionResult 商品一覧()
        {
            // ビューモデルを生成
            var model = new 商品区分sViewModel();
            return View(model);
        }

        // 商品一覧をPartialViewで返します。
        [AjaxOnly]
        public ActionResult getpv商品一覧([Bind(Prefix = "List商品区分")] Nullable <int> 区分コード) 
        {
            //[AjaxOnly]があるから以下のIsAjaxRequest判定は不要かも?
            if (Request.IsAjaxRequest()) 
            {
                // ビューモデルを生成
                IList<VM_商品> model = new List<VM_商品>();
                var listitem = new 商品sViewModel((int) 区分コード);
                model = listitem.商品s;
                return PartialView("_商品一覧", model);
            }
            //AjaxRequestじゃない場合。return new EmptyResult();の方がいいかも?
            return RedirectToAction("Index", "Home"); 
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }

        //Attribute[AjaxOnly]を付けてみました。
        public class AjaxOnlyAttribute : ActionMethodSelectorAttribute
        {
            public override bool IsValidForRequest(
            ControllerContext controllerContext, MethodInfo methodInfo)
            {
                return controllerContext.HttpContext.Request.IsAjaxRequest();
            }
        }
    }
}

※注 上記スクリプト、"(",")"がうまく張り付かないので全角で書いてます。

特徴的なのは、商品一覧をPartialViewで返す部分ですね。みようみまねでやってみてますんで甚だ不安ですが、これで一応デバッグモードでは動いてます。

[AjaxOnly]って属性を付けてみました。この属性は標準ものではなく、スクリプトの最後の部分にある public class AjaxOnlyAttribute って部分がその中身です。細かいことが説明できるほどのスキルはありません、ごめんね。取り敢えずこの属性を付けることで直接URLを入力された場合に部分ビューが呼び出されることを防止できてます。

以下のページを参考にさせてもらいました。

AjaxOnly actions and controllers in Asp.Net MVC
http://stevescodingblog.co.uk/ajaxonly-actions-and-controllers-in-asp-net-mvc/

一方、 if (Request.IsAjaxRequest) って記述がありますがこれはこれで動作してます。ソースのコメントにも書いてますが、先の[AjaxOnly]属性と重複してる感があります。このあたりは例外処理のお好みでってことになるのかもしれません。

 

 5.[Views]フォルダにビューを作成

[Views]フォルダーに[商品]フォルダを追加し、ビュー[商品一覧.cshtml]と部分ビュー[_商品一覧.cshtml]を作成。

まずは部分ビュー、このビューの作成はスキャフォールディングが便利。
商品Controller.csのスクリプトを表示した状態で、スクリプト上で右クリック[ビューの追加]を選択し[ビューの追加]ダイアログを表示。

モデルクラス:VM_商品、

スキャフォールディングビューテンプレート:List、

部分ビューとして作成するをチェックして、[追加]でビューを追加した後不要な部分を削除して出来上がりです。

<_商品一覧.cshtml>

@model IEnumerable<Mvc4ApplicationE.Models.VM_商品>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.商品コード)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.商品名)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.区分コード)
        <th></th>
    </tr>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.商品コード)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.商品名)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.区分コード)
        </td>
   </tr>
}

</table>

次に、本体側ですが、こっちはほぼ手作りしました。

<商品一覧.cshtml>

@{
    ViewBag.Title = "商品一覧";
}
@model Mvc4ApplicationE.Models.商品区分sViewModel

<h2>@ViewBag.Title</h2>

@using (Ajax.BeginForm("getpv商品一覧", "商品",
   new AjaxOptions
   {
       UpdateTargetId = "pv商品一覧",
       LoadingElementId = "loading"
   }
   ))
{

    @*商品区分のDropDownList初期値は区分コード= 0 のすべてを指定してます。*@ 
    @Html.DropDownList("List商品区分", new SelectList(Model.商品区分s,"Value","Text",0))

    <input type="submit" name="btnEdit" value="表示" />
    <img id="loading" src="@Url.Content("~/images/loadinfo.net.gif")" alt="" class="loader" style="display: none;"  /> 
}
<hr>
<div id="pv商品一覧"></div> @*ここに商品の一覧が挿入される *@

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")

※注 上記スクリプト、"(",")"がうまく張り付かないので全角で書いてます。

Ajax.BeginFormがポイントになるんでしょうか。この指定で部分ビューの更新を行います。BeginFormのオーバーロードは10種類あるようですが、今回の例での引数の部分は、アクション名:getpv商品一覧、コントローラー名:商品、そしてAjaxOptionsの3つを渡してます。

詳細はこちら

AjaxExtensions.BeginForm Method (AjaxHelper, String, String, AjaxOptions)

http://msdn.microsoft.com/en-us/library/dd505270(v=vs.98).aspx

今回の例では、一つめのAjaxHelperは省略した形式です。

で、オプションクラスの部分ですが、今回は特にJavaScriptでの「前処理」や「後処理」等が不要なので、UpdateTargetIDとLoadingElementIdのみを指定してます。

UpdateTargetIDは、部分ビューをセットする要素名で、上記の場合

<div id="pv商品一覧"></div> この空部分に入ってきます。

LoadingElementIdはリクエストの実行中に表示する要素があれば指定します。

上記の例では、"loading"で以下の部分に相当します。

<img id="loading" src="@Url.Content("~/images/loadinfo.net.gif")"..以降省略

今回の例では、ぐるぐる回るアニメーションを指定してみました。本体ビューが表示された時点でぐるぐる回ってしまうので、style="display: none;"を付加してます。

あと、Ajax関連ではスクリプト最後の

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
こんな指定が必要です。もしくは、

<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"    ></script>
こんな記述でもOKでした。

MVC4では標準でバンドルされてるんで、@Scripts.Renderを採用してます。

商品区分のドロップダウンリストは、

@Html.DropDownList("List商品区分", new SelectList(Model.商品区分s,"Value","Text",0)

ドロップダウンリストの実装方法はいろんな方法があるようですが、SelectListを使うこの例はシンプルで気に入ってます。

引数最後の 0 は無くてもいいです。ここはリストの初期値をセットできます。例えば2とか指定するとValue=2が選択された状態で初期表示されます。modelやViewBagなどでセットすることになるかと思います。

 なお、"loading"のぐるぐる回るアニメーションは以下のサイトから調達させてもらいました。

http://loadinfo.net/

以上です。