喜帳面の日記

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

Visual Studio Express 2012 for Web でいってみる 17.PartialViewとページング

今回は、前回の続きです。
 初心者が行き当たりばったりにいろいろ動かしてみたときのメモなので記載内容には勝手な解釈や誤りもあるかと思います。そのあたりのご指摘よろしくお願いします。

前回は、
『一覧表示に絞り込み条件を付けて一覧部分をPartialViewで表示する』でした。

今回は、『一覧にページング機能を付けたい。』です。『前ページ』『次ページ』といったボタンかリンクを付けてみました。

出来上がりの画面はこんな感じになりました。

f:id:SannomiyaNotes:20121225153054p:plain

 参考にした情報は今回も

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

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

前回のページング無し状態から今回のページング機能の実装の為の改定は、ほぼこのサンプルからいただきました。


ページング機能のコア的なロジック部分は上記サンプルの[Models]ー[Helpers]フォルダにある[PagedCollection.cs]をほぼそのまま利用させてもらいました。

サンプルと同様に、[Models]ー[Helpers]フォルダに格納しています。

 『PagedCollectionクラス』

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

namespace Mvc4ApplicationE.Models.Helpers
{
    // ページング可能なアイテムコレクション
    public class PagedCollection<T> : IEnumerable<T>
    {
        private List<T> pagedList;

        public PagedCollection(IQueryable<T> source, int pageIndex, int pageSize)
        {
            if (source == null) { throw new ArgumentNullException("source"); }
            if (pageIndex < 0) { throw new ArgumentOutOfRangeException("pageIndex"); }
            if (pageSize < 1) { throw new ArgumentOutOfRangeException("pageSize"); }

            TotalCount = source.Count();
            TotalPages = (int)Math.Ceiling(TotalCount / (double)pageSize);

            if (TotalCount == 0)
            {
                PageIndex = 0;
                PageSize = 0;
            }
            else
            {
                if (pageIndex >= TotalPages) { throw new ArgumentOutOfRangeException("pageIndex"); }

                PageIndex = pageIndex;
                PageSize = pageSize;
                this.pagedList = source.Skip(PageIndex * PageSize).Take(PageSize).ToList();
            }
        }

        // アクティブなページのインデックス
        public int PageIndex { get; private set; }
        // 1ページあたりのアイテム数
        public int PageSize { get; private set; }
        // 総アイテム数
        public int TotalCount { get; private set; }
        // 総ページ数
        public int TotalPages { get; private set; }
        // 前のページが存在するか否か
        public bool HasPreviousPage { get { return (PageIndex > 0); } }
        // 次のページが存在するか否か
        public bool HasNextPage { get { return (PageIndex + 1 < TotalPages); } }

        // 前のページのインデックスを取得
        public int GetPreviousPageIndex()
        {
            if (!HasPreviousPage) { throw new InvalidOperationException(); }

            return PageIndex - 1;
        }

        // 次のページのインデックスを取得
        public int GetNextPageIndex()
        {
            if (!HasNextPage) { throw new InvalidOperationException(); }

            return PageIndex + 1;
        }

        // IEnumerable<T>.GetEnumerator の実装
        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            foreach (var item in this.pagedList)
            {
                yield return item;
            }
        }

        // IEnumerable.GetEnumerator の実装
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<T>)this).GetEnumerator();
        }
    } // end of class
} // end of namespace

 

『Model』

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

//以下のNameSpaceを追加
using System.Web.Mvc;
using System.ComponentModel;
//page対応で追加
using Mvc4ApplicationE.Models.Helpers;
 
namespace Mvc4ApplicationE.Models
{
    public class 商品区分sViewModel
    {
        private NorthwindJEntities db = new NorthwindJEntities();
        //コンストラクターでデータを取得して商品区分一覧リスト
        public 商品区分sViewModel()
        {
            // --- 選択可能な商品区分リストの生成 ---
            var 商品区分SelectItemList = new List<SelectListItem>() 
            { 
                // "すべて"を表す商品区分をリストの先頭に追加
                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<SelectListItem> 商品区分s { get; private set; }
    }
    /// <summary>
    /// 商品の一覧用
    /// </summary>
    public class VM_商品
    {
        [DisplayName("コード")]
        public int 商品コード { get; set; }

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

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

    public class 商品sViewModel  
    {
        private NorthwindJEntities db = new NorthwindJEntities();
        //コンストラクターでデータをセット
        public 商品sViewModel(int 区分コード, int pageIndex) //page
        {
            int 区分コードMIN = 区分コード;
            int 区分コードMAX = 区分コード;
            if (区分コード == 0) //全ての場合、MINとMAXをデータから取得する。
            {
                区分コードMIN = (from t in db.商品区分 select t.区分コード).Min();
                区分コードMAX = (from t in db.商品区分 select t.区分コード).Max();
            }
            //page対応
            //データを取得してsourceに格納
            IQueryable<商品> source = FindProducts(区分コードMIN, 区分コードMAX);
            //指定のページのListを取得 1頁当りの行数は10に固定してます。↓)
            var pagedList = new PagedCollection<商品>(source, pageIndex, 10);

            //指定された区分コードをModelにセット
            conditionKBN = 区分コード;
            //0件チェック
            if (pagedList.TotalCount != 0)
            {
                IList<VM_商品> 商品SelectItemList = new List<VM_商品>();
                foreach (var s in pagedList)
                {
                    var item = new VM_商品();
                    item.商品コード = s.商品コード;
                    item.商品名 = s.商品名;
                    item.区分コード = (int)s.区分コード;
                    商品SelectItemList.Add(item);
                }
                Products = 商品SelectItemList;
            }
            Page = pagedList.PageIndex + 1;
            PageIndex = pagedList.PageIndex;
            PageSize = pagedList.PageSize;
            TotalCount = pagedList.TotalCount;
            TotalPages = pagedList.TotalPages;
            HasPreviousPage = pagedList.HasPreviousPage;
            HasNextPage = pagedList.HasNextPage;
        }
        //指定された区分コード
        public int conditionKBN { get; private set; }
        //商品リスト
        //public IList<VM_商品> Products { get; private set; }
        public IEnumerable<VM_商品> Products { get; private set; }
        // アクティブなページ
        public int Page { get; private set; }
        // アクティブなページのインデックス
        public int PageIndex { get; private set; }
        // 1ページあたりのアイテム数
        public int PageSize { get; private set; }
        // 総アイテム数
        public int TotalCount { get; private set; }
        // 総ページ数
        public int TotalPages { get; private set; }
        // 前のページが存在するか否か
        public bool HasPreviousPage { get; private set; }
        // 次のページが存在するか否か
        public bool HasNextPage { get; private set; }
        
        public IQueryable<商品> FindProducts(int 区分コードMIN, int 区分コードMAX)
        {
            // 生産中止になっていない指定の区分の商品を取得するクエリー
            return
                     from s in this.db.商品
                     where s.区分コード >= 区分コードMIN && s.区分コード <= 区分コードMAX
                     orderby s.商品コード
                     select s;
        }

    }
}
    

Modelには、新しくページングで使用するプロパティを追加し、コンストラクターでセットしています。

 『コントローラー』

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
//以下のNameSpaceを追加
using System.Reflection;
using Mvc4ApplicationE.Models;

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> 区分コード
                          , int? conditionKBN
                          , int? page
                          , string Button) 
        {
            if (Request.IsAjaxRequest()) //[AjaxOnly]があるから不要かも?
            {
                int pageIndex = (page ?? 0);
                switch (Button)
                {
                    case "Previous":
                        区分コード = conditionKBN;
                        pageIndex = (page ?? 0) - 1;
                        break;
                    case "Next":
                        区分コード = conditionKBN;
                        pageIndex = (page ?? 0) + 1;
                        break;
                    default:
                        break;
                }
                // ビューモデルを生成
                商品sViewModel model = new 商品sViewModel((int)区分コード, pageIndex);//page
                if (model.TotalCount == 0)
                {
                    ModelState.AddModelError("", "条件に一致するデータは見つかりませんでした。");
                }
                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();
            }
        }
    }
}

注:都合によりかっこは全角に置換してます。

改造部分は、

public ActionResult getpv商品一覧の引数に、conditionKBN、page、Buttonを追加してます。これらの項目は、『次頁』『前頁』がクリックされて再び、コントローラーに戻ってきたときに必要になる『抽出条件の商品区分』、『表示中のページ』、『『次頁』『前頁』の判断材料』です。

『switch (Button)』の部分でクリックされたボタン・リンクを判断し、次に表示するページ番号(pageIndex)の設定を行ってます。

そして、// ビューモデルを生成

商品sViewModel model = new 商品sViewModel((int)区分コード, pageIndex);

にて、次の1ページ分のデータをモデルにセットしています。

 

『ビュー 本体』部分ビュー(商品一覧)を含むビュー

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

<h2>@ViewBag.Title</h2>

@using (Ajax.BeginForm
    ("getpv商品一覧", "商品",
      new AjaxOptions
      {
         HttpMethod = "POST",
         UpdateTargetId = "pv商品一覧",
         LoadingElementId = "loading"
      }))
   
{
        @*商品区分のDropDownList初期値は0のすべてを指定してます。*@ 
        @Html.DropDownList("List商品区分", 
                          new SelectList(Model.商品区分s,"Value","Text",ViewBag.Select))
    <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"

前回と同じです。確か変更してないと思います。

『部分ビュー』部分ビュー(商品一覧自体)

@model Mvc4ApplicationE.Models.商品sViewModel
@Html.ValidationSummary(true)

@if (Model.TotalCount != 0)
{
    <table>
        @foreach (var item in Model.Products.Take(1)) {
            <tr>
                <td>
                    @Html.DisplayNameFor(modelItem => item.商品コード)
                </td>
                <td>
                    @Html.DisplayNameFor(modelItem => item.商品名)
                </td>
                <td>
                    @Html.DisplayNameFor(modelItem => item.区分コード)
                </td>
            </tr>
    }

    @foreach (var itemd in Model.Products) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => itemd.商品コード)
            </td>
            <td>
                @Html.DisplayFor(modelItem => itemd.商品名)
            </td>
            <td>
                @Html.DisplayFor(modelItem => itemd.区分コード)
            </td>
          </tr>
    }
    </table>

    {
        if (Model.HasPreviousPage) 
        {    
            @Ajax.ActionLink("<<",
                             "getpv商品一覧",
                             new { conditionKBN = Model.conditionKBN,
                                   page = Model.PageIndex,
                                   Button="Previous"},
                             new AjaxOptions() {UpdateTargetId = "pv商品一覧" })
        }
        else
        {
            <span class="no-link"><<</span>
        }
        if (Model.HasNextPage) 
        {
            @Ajax.ActionLink(">>",
                             "getpv商品一覧",
                             new { conditionKBN = Model.conditionKBN,
                                   page = Model.PageIndex,
                                   Button="Next"},
                             new AjaxOptions() {UpdateTargetId = "pv商品一覧" })
        }
        else
        {
            <span class="no-link">>></span>
        }
       
        <tr>
           <td>
               @Html.DisplayFor(modelItem => Model.Page)
          </td>
           <td>
               /
          </td>
           <td>
               @Html.DisplayFor(modelItem => Model.TotalPages)
          </td>
        </tr>
    }
}

 

『前ページ』『次ページ』の部分は「@Ajax.ActionLink」を使用してます。

引数の部分は、"<<"と表示し、アクションは"getpv商品一覧"を実行、次の

new { conditionKBN = Model.conditionKBN,
          page = Model.PageIndex,
          Button="Previous"}

これらを、メソッドに渡しています。

もし、『前ページ』『次ページ』をリンクじゃなくてボタンにしたい場合は、

Ajax.BeginFormで対応できます。こんな感じです。

    @using (Ajax.BeginForm( 
         "getpv商品一覧",
         "商品", 
         new { conditionKBN = Model.conditionKBN
             , page = Model.PageIndex },
         new AjaxOptions
             {
              UpdateTargetId = "pv商品一覧",
              LoadingElementId = "loading"
            }))
      {
            if (Model.HasPreviousPage) 
            {    
               <input type="submit" name="Button" value="Previous" />
            }
            if (Model.HasNextPage) 
            {
                <input type="submit" name="Button" value="Next" />
            }
        
        <tr>
           <td>
               @Html.DisplayFor(modelItem => Model.Page)
          </td>
           <td>
               /
          </td>
           <td>
               @Html.DisplayFor(modelItem => Model.TotalPages)
          </td>
        </tr>
      }

以上、MVCでのリストのページングサンプルでした。

と、ここまでやってきたものの、この後に、CSSjQuery使って見た目の調整するんだと思うと、はなっから高機能なjQueryのプラグインを使った方がいいのかな?なんて気持ちになってきました。

今回は以上です。