2015年2月8日 星期日

練習題 - ASP.NET MVC 使用政府公開資料 - 分頁使用 AJAX + PartialView

有關資料分頁的做法已經寫了許多篇文章來說明,而這一次的做法其實跟之前的也沒有什麼不同,使用的方法也沒有什麼不同,這邊只是使用「ASP.NET MVC 使用政府公開資料」這個範例繼續做延伸,在現有的範例程式裡再去做變化,而且「ASP.NET MVC 使用政府公開資料」這個範例有使用到 Async/Await 的非同步做法,不過實際在做的時候其實也與沒有使用非同步的一般做法是沒有多大的差別。

ASP.NET MVC 使用政府公開資料
ASP.NET MVC 資料分頁 - 使用 PagedList.Mvc:AJAX


「ASP.NET MVC 使用政府公開資料」範例程式可以到 Github 去下載,

https://github.com/kevintsengtw/MVC-NewTaipeiCity-OpenData-Sample

image

 

建立 Controller 與 View

新增加一個 HotSpot2Controller,使用的是新增加一個 MVC 5 的空白控制器

image

新增 Index 的檢視頁面,建立新的 Index.cshtml 之後再將 HotSpot/Index.cshtml 的內容複製過來,

image

 

建立 PartialView

接著在 ~/Views/HotSpot2/ 資料夾新增部分檢視(PartialView),

SNAGHTML9a09809

因為我們是把分頁的內容給放在 PartialView 裡,所以將 Index.cshtml 裡的 Pager 與資料呈現的 Table 給移到 PartialView 內,

_PagedPartial.cshtml

@using PagedList
@using PagedList.Mvc
@model PagedList.StaticPagedList<Sample.Models.HotSpot>
 
@if (Model != null && Model.Count > 0)
{
    @Html.PagedListPager((IPagedList)Model,
        page => Url.Action("Index", new
        {
            page = page,
            districts = ViewBag.SelectedDistrict,
            types = ViewBag.SelectedType,
            company = ViewBag.SelectedCompany
        }),
        PagedListRenderOptions.ClassicPlusFirstAndLast)
 
    <table class="table table-striped table-hover">
        <thead>
            <tr style="background-color: #d3efff;">
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Spot_Name)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Type)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Company)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().District)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Address)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Apparatus_Name)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Latitude)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstOrDefault().Longitude)
                </th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.Spot_Name)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Type)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Company)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.District)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Address)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Apparatus_Name)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Latitude)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Longitude)
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

而整理過後的 Index.cshtml 如下

@{
    ViewBag.Title = "Index";
}
 
<div class="row">
    <h2>
        練習題 - 新北市 WIFI 熱點
        <small>
            資料來源:
            <a href="http://data.ntpc.gov.tw/NTPC/" target="_blank">
                新北市政府資料開放平台
            </a>
            -
            <a href="http://data.ntpc.gov.tw/NTPC/od/query?_m=detail&oid=04958686-1B92-4B74-889D-9F34409B272B" target="_blank">
                新北市WIFI熱點
            </a>
        </small>
    </h2>
</div>
<hr />
<div class="row well">
    <form class="form" role="form">
        <div class="col-lg-3">
            <div class="input-group">
                <span class="input-group-addon">區域</span>
                @Html.DropDownList("Districts", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-3">
            <div class="input-group">
                <span class="input-group-addon">熱點分類</span>
                @Html.DropDownList("Types", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-4">
            <div class="input-group">
                <span class="input-group-addon">業者</span>
                @Html.DropDownList("Companys", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-2">
            <button type="submit" id="ButtonSubmit" class="btn btn-primary">
                <span class="glyphicon glyphicon-search"></span> 查詢
            </button>
        </div>
    </form>
</div>
 
 

 

完成 Controller 的程式

再來是要把 HotSpot2Controller 的程式給完成,這部分的程式與原本的 HotSpotController 會不同,這邊主要的資料取得與分頁處理不會是在 Index Action 方法中,而是會在一個專門處理 PartialView 的 Action 方法,不過這邊不會再把取得公開資料的程式放在 Controller 裡,而是抽出來然後放在 Models 裡,就先來處理這一個部分。

WifiDataService.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Runtime.Caching;
using System.Threading.Tasks;
using ServiceStack;
using System.Web.Mvc;
 
namespace Sample.Models
{
    public class WifiDataService
    {
        private string _TargetUri = string.Empty;
        private string _CacheName = string.Empty;
 
        public WifiDataService(
            string targetUri = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json",
            string cacheName = "WIFI_HOTSPOT")
        {
            this._TargetUri = targetUri;
            this._CacheName = cacheName;
        }
 
        /// <summary>
        /// Gets the hot spot data.
        /// </summary>
        /// <returns></returns>
        public async Task<IEnumerable<HotSpot>> GetHotSpotData()
        {
            ObjectCache cache = MemoryCache.Default;
 
            if (cache.Contains(_CacheName))
            {
                var cacheContents = cache.GetCacheItem(_CacheName);
                return cacheContents.Value as IEnumerable<HotSpot>;
            }
            else
            {
                return await RetriveHotSpotData(_CacheName);
            }
        }
 
        public async Task<IEnumerable<HotSpot>> RetriveHotSpotData(string cacheName)
        {
            var client = new HttpClient
            {
                MaxResponseContentBufferSize = Int32.MaxValue
            };
 
            var response = await client.GetStringAsync(_TargetUri);
 
            //=====================================================================================
 
            var sw = new Stopwatch();
            sw.Start();
 
            //使用 ServiceStack.Text
            var collection = response.FromJson<IEnumerable<HotSpot>>();
 
            sw.Stop();
            var ts = sw.Elapsed;
 
            var elapsedTime = String.Format("{0:00}h : {1:00}m :{2:00}s .{3:000}ms",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            Debug.WriteLine("RunTime: " + elapsedTime);
 
            //=====================================================================================
 
            //資料快取
            ObjectCache cacheItem = MemoryCache.Default;
 
            var policy = new CacheItemPolicy
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(30)
            };
 
            cacheItem.Add(cacheName, collection, policy);
 
            return collection;
        }
 
        /// <summary>
        /// Gets the select list.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="selectedItem">The selected item.</param>
        /// <returns></returns>
        public List<SelectListItem> GetSelectList(
            IEnumerable<string> source,
            string selectedItem)
        {
            var selectList = source.Select(item => new SelectListItem()
            {
                Text = item,
                Value = item,
                Selected = !string.IsNullOrWhiteSpace(selectedItem)
                           &&
                           item.Equals(selectedItem, StringComparison.OrdinalIgnoreCase)
            });
            return selectList.ToList();
        }
 
        /// <summary>
        /// 取得區域資料.
        /// </summary>
        /// <returns></returns>
        public async Task<List<string>> GetDistricts()
        {
            var source = await this.GetHotSpotData();
            if (source == null) return new List<string>();
 
            var districts = source.OrderBy(x => x.District)
                                  .Select(x => x.District)
                                  .Distinct();
 
            return districts.ToList();
        }
 
        /// <summary>
        /// 取得熱點分類.
        /// </summary>
        /// <returns></returns>
        private async Task<List<string>> GetHotSpotTypes()
        {
            var source = await this.GetHotSpotData();
            if (source == null) return new List<string>();
 
            var types = source.OrderBy(x => x.Type)
                              .Select(x => x.Type)
                              .Distinct();
 
            return types.ToList();
        }
 
        /// <summary>
        /// 取得業者資料.
        /// </summary>
        /// <returns></returns>
        public async Task<List<string>> GetCompanys()
        {
            var source = await this.GetHotSpotData();
            if (source == null) return new List<string>();
 
            var companys = source.OrderBy(x => x.Company)
                                 .Select(x => x.Company)
                                 .Distinct();
 
            return companys.ToList();
        }
    }
}

HotSpot2Controller.cs

using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
using PagedList;
using Sample.Models;
 
namespace Sample.Controllers
{
    public class HotSpot2Controller : Controller
    {
        const string TargetUri = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json";
        const string CacheName = "WIFI_HOTSPOT";
 
        public WifiDataService service
        {
            get
            {
                return new WifiDataService(TargetUri, CacheName);
            }
        }
 
        public async Task<ActionResult> Index(
            int? page,
            string districts,
            string types,
            string companys)
        {
            //區域
            var districtData = await service.GetDistricts();
            ViewBag.Districts = service.GetSelectList(districtData, districts);
            ViewBag.SelectedDistrict = districts;
 
            //熱點分類
            var hotspotTypeData = await service.GetHotSpotTypes();
            var typeSelectList = service.GetSelectList(hotspotTypeData, types);
            ViewBag.Types = typeSelectList.ToList();
            ViewBag.SelectedType = types;
 
            //業者
            var companyData = await service.GetCompanys();
            var companySelectList = service.GetSelectList(companyData, companys);
 
            ViewBag.Companys = companySelectList.ToList();
            ViewBag.SelectedCompany = companys;
 
            return View();
        }
 
        public async Task<PartialViewResult> PagedPartial(
            int? page,
            string districts,
            string types,
            string companys)
        {
            var source = await service.GetHotSpotData();
            source = source.AsQueryable();
 
            if (!string.IsNullOrWhiteSpace(districts))
            {
                source = source.Where(x => x.District == districts);
            }
            if (!string.IsNullOrWhiteSpace(types))
            {
                source = source.Where(x => x.Type == types);
            }
            if (!string.IsNullOrWhiteSpace(companys))
            {
                source = source.Where(x => x.Company == companys);
            }
 
            int pageIndex = page ?? 1;
            int pageSize = 10;
            int totalCount = 0;
 
            totalCount = source.Count();
 
            source = source.OrderBy(x => x.District)
                           .Skip((pageIndex - 1) * pageSize)
                           .Take(pageSize);
 
            var pagedResult =
                new StaticPagedList<HotSpot>(source, pageIndex, pageSize, totalCount);
 
            return PartialView("_PagedPartial", pagedResult);
        }
    }
}

這邊要注意的是,在 PagedPartial Action 方法也一樣是使用了 async 非同步處理,

image

最後回傳 PartialView 的部分

image

 

修改 View

這邊是要使用 AJAX 的方式去更換分頁的內容,所以要在 View 裡增加前端的事件處理,

@{
    ViewBag.Title = "Index";
}
<div class="row">
    <h2>
        練習題 - 新北市 WIFI 熱點 - AJAX 分頁, 使用 PartialView
        <br/>
        <small>
            資料來源:
            <a href="http://data.ntpc.gov.tw/NTPC/" target="_blank">
                新北市政府資料開放平台
            </a>
            -
            <a href="http://data.ntpc.gov.tw/NTPC/od/query?_m=detail&oid=04958686-1B92-4B74-889D-9F34409B272B" target="_blank">
                新北市WIFI熱點
            </a>
        </small>
    </h2>
</div>
<hr />
<div class="row well">
    <form class="form" role="form">
        <div class="col-lg-3">
            <div class="input-group">
                <span class="input-group-addon">區域</span>
                @Html.DropDownList("Districts", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-3">
            <div class="input-group">
                <span class="input-group-addon">熱點分類</span>
                @Html.DropDownList("Types", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-4">
            <div class="input-group">
                <span class="input-group-addon">業者</span>
                @Html.DropDownList("Companys", null, "全部", new { @class = "form-control" })
            </div>
        </div>
        <div class="col-lg-2">
            <button type="button" id="ButtonSubmit" class="btn btn-primary">
                <span class="glyphicon glyphicon-search"></span> 查詢
            </button>
        </div>
    </form>
</div>
 
<div id="WifiData"></div>
 
@section scripts
{
    <script type="text/javascript">
 
        $(function () {
            var page = window.location.hash
                ? window.location.hash.slice(1)
                : 1;
 
            fetchPage(page);
 
            $('#ButtonSubmit').click(function () {
                fetchPage(page);
            });
        });
 
        var fetchPage = function (page) {
            var pagedPartialUrl = '@Url.Action("PagedPartial", "HotSpot2")';
 
            var district = $('#Districts').val();
            var type = $('#Types').val();
            var company = $('#Companys').val();
 
            $.get(pagedPartialUrl, { page: page, districts: district, types: type, companys: company },
                function (data) {
                    window.location.hash = page;
 
                    $('#WifiData').html(data);
 
                    $('#WifiData .pagination li a').each(function (i, item) {
                        var hyperLinkUrl = $(item).attr('href');
                        if (typeof hyperLinkUrl !== 'undefined' && hyperLinkUrl !== false) {
                            var pageNumber = $(item).attr('href').replace('/HotSpot2?page=', '');
                            $(item).attr('href', '#').click(function (event) {
                                event.preventDefault();
                                $(event.target).attr('href');
                                fetchPage(pageNumber);
                            });
                        }
                    });
                });
        };
    </script>
}

 

執行結果

image

在下圖的內容可以看出,每次的分頁與查詢的資料取得、顯示都是透過 PagedPartial Action 方法

image

 


這一篇真的只是將以前所做的再拿出來組合應用與練習,差別只在於資料來源不是從資料庫而是從政府公開資料所提供的 JSON,另外就是使用了 Async/Await 的非同步處理,實作上與一般使用資料庫和同步處理並沒有什麼不同。

另外這一篇的實作程式內容就不更新到 Github 上。

 

相關連結

ASP.NET MVC 使用政府公開資料

ASP.NET MVC 資料分頁 - 使用 PagedList.Mvc:AJAX

 

以上

3 則留言:

  1. 請問ServiceStack需要安裝哪個套件嗎?

    回覆刪除
    回覆
    1. 在這個範例專案的 Github Repository Readme 裡不都有說了嗎?

      刪除
    2. 抱歉 沒有看到這個 剛剛找到了 謝謝

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。