2013年11月26日 星期二

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

現在越來越多政府單位有提供公開資料,我們可以拿這些公開資料來做一些工具或是服務,大部分的政府公開資料都會提供 JSON 或是 XML 格式的資料,也有一些是直接提供檔案讓我們下載使用,所以這一次就來練習怎麼在 ASP.NET MVC 網站裡使用政府公開資料,這裡將會使用「新北市政府資料開放平台」的「新北市 WIFI 熱點」來作為這次練習的資料來源。

 


開發環境:Visual Studio 2013, ASP.NET MVC 5

資料來源:新北市政府資料公開平台 – 新北市WIFI熱點

image

image

這裡有提供以 URL 取得 JSON, XML, CSV,還有下載 Excel 檔案的方式,在範例裡將會使用以 URL 取得 JSON 資料的方式。

 

首先在建立好的空白 ASP.NET MVC 專案裡加入一個 HotSpotController,

image

接著我們要使用 HttpClient 類別來傳送 HTTP 要求以及接收 HTTP 回應,HttpClient 類別是 .NET Framework 4.5 才有提供的,與以往 WebClient 類別最大的不同在於「非同步」,HttpClient 類別的方法大部分都是非同步作業方式,這邊將會使用 GetStringAsync(String) 來取得 JSON 資料。

MSDN - HttpClient 類別 (System.Net.Http)

MSDN - HttpClient.GetStringAsync 方法 (String) (System.Net.Http)

要特別注意的是,因為使用了 HttpClient.GetStringAsync() 取得指定 URI 所回傳的資料,使用了非同步作業的方式,所以原本的 Action 方法也需要修改為非同步。

image

檢視頁面就很簡單,把取回的 JSON 字串顯示出來,

image

執行結果

image

 

但是這樣的 JSON 字串並不能直接拿來使用,所以要反序列化還原為物件,這邊我們不必自己用手動的方式去建立類別,可以使用「編輯 > 選擇性貼上 > 貼上 JSON 做為類別」的方式來建立,

image

先複製 JSON 字串裡的一個物件的資料「{"id":"ZZZITWF100261","spot_name":"中和錦和運動公園-司令台 -3","type":"NewTaipei","company":"全球一動","district":"中和區","address":"235新北市中和區錦和路350號","apparatus_name":"新北市中和區公所","latitude":"24.99249","longitude":"121.49033"}」,然後在 Models 目錄裡建立一個 HotSpot 類別的檔案,再來就是使用「編輯 > 選擇性貼上 > 貼上 JSON 做為類別」建立類別,如下:

image

依據「新北市WIFI熱點」頁面上的欄位定義,稍做修改,

image

image

public class HotSpot
{
    [Display(Name = "熱點代碼")]
    public string ID { get; set; }
 
    [Display(Name = "熱點名稱")]
    public string Spot_Name { get; set; }
 
    [Display(Name = "熱點分類")]
    public string Type { get; set; }
 
    [Display(Name = "業者")]
    public string Company { get; set; }
 
    [Display(Name = "鄉鎮市區")]
    public string District { get; set; }
 
    [Display(Name = "地址")]
    public string Address { get; set; }
 
    [Display(Name = "機關名稱")]
    public string Apparatus_Name { get; set; }
 
    [Display(Name = "緯度")]
    public string Latitude { get; set; }
 
    [Display(Name = "經度")]
    public string Longitude { get; set; }
}

 

原本 HotSpotController Index Action 方法就可以使用 JSON.NET 的 JsonConvert.DeserializeObject<T>() 來反序列化 JSON 字串,以取得 HotSpot Collection,

image

檢視頁面也做了修改,改以 Table 來顯示資料,

@model IEnumerable<BlogSample.Models.HotSpot>
 
@{
    ViewBag.Title = "Index";
}
 
<h2>新北市 WIFI 熱點</h2>
 
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Spot_Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Type)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Company)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.District)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Address)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Apparatus_Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Latitude)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Longitude)
        </th>
    </tr>
 
    @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>
    }
 
</table>

執行結果

image

 

但每次要執行時就必須要到遠端取得資料,所以我們可以在第一次取得資料後放到快取裡,之後讀取資料就從快取裡取得,當快取資料的期限過了或是快取資料不見就再到遠端資料來源去拿。

這邊的快取並不是使用 System.Web.Caching 而是使用 System.Runtime.Caching,

MSDN - System.Runtime.Caching 命名空間

要使用 System.Runtime.Caching 要另外加入組件參考,

SNAGHTMLbdd9de

 

到指定 URI 位置取得資料並且將資料置入 Cache 裡,快取是放在記憶體中,然後設定快取的期限為 30 分鐘,

MSDN - CacheItemPolicy 類別 (System.Runtime.Caching)

image

修改後的 Index Action 方法

image

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.Caching;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using BlogSample.Models;
using Newtonsoft.Json;
 
namespace BlogSample.Controllers
{
    public class HotSpotController : Controller
    {
        public async Task<ActionResult> Index()
        {
            var hotSpotSource = await this.GetHotSpotData();
            ViewData.Model = hotSpotSource;
            return View();
        }
 
        private async Task<IEnumerable<HotSpot>> GetHotSpotData()
        {
            string cacheName = "WIFI_HOTSPOT";
 
            ObjectCache cache = MemoryCache.Default;
            CacheItem cacheContents = cache.GetCacheItem(cacheName);
 
            if (cacheContents == null)
            {
                return await RetriveHotSpotData(cacheName);
            }
            else
            {
                return cacheContents.Value as IEnumerable<HotSpot>;
            }
        }
 
        private async Task<IEnumerable<HotSpot>> RetriveHotSpotData(string cacheName)
        {
            string targetURI = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json";
 
            HttpClient client = new HttpClient();
            client.MaxResponseContentBufferSize = Int32.MaxValue;
            var response = await client.GetStringAsync(targetURI);
 
            var collection = JsonConvert.DeserializeObject<IEnumerable<HotSpot>>(response);
 
            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = DateTime.Now.AddMinutes(30);
 
            ObjectCache cacheItem = MemoryCache.Default;
            cacheItem.Add(cacheName, collection, policy);
 
            return collection;
        }
    }
}

 


這一篇就先講到這裡,除了政府公開資料的取得與使用之外,最主要的是這個練習中用了幾個操作方式:Async/Await, HttpClient, SystemRuntime.Caching,下一篇再完成這個練習的其他部份。

 

參考連結:

MSDN - HttpClient 類別 (System.Net.Http)

MSDN - HttpClient.GetStringAsync 方法 (String) (System.Net.Http)

MSDN - System.Runtime.Caching 命名空間

MSDN - CacheItemPolicy 類別 (System.Runtime.Caching)

 

以上

14 則留言:

  1. mrkt:

    初訪貴舍,按照您寫的內容實作了一遍,丟回的物件是 "System.Net.Http.StreamContent+ReadOnlyStream" (使用網際網路應用程式專案) 與找不到路徑 (使用基本專案),請問前輩是有那步驟有缺少?

    回覆刪除
    回覆
    1. 你所提供的訊息並不是能夠讓我做更進一步的分析與釐清,也不曉得你的開發環境為何,
      我一開始有提到我所使用的開發環境為「Visual Studio 2013, ASP.NET MVC 5」
      另外補充所使用的 .NET Framework 為 4.5.1,然後這一篇的內容並未使用到第三方套件。

      我的研判是.... 你用的是 GetStreamAsync 方法,而並不是我範例程式裡所使用的 GetStringAsync 方法,
      所以就會出現你所說的丟回來的是「System.Net.Http.StreamContent+ReadOnlyStream」類別的物件,
      對方並不是給我們 Stream 資料,而是一般的 JSON 文字內容,所以只要使用 GetStringAsync 方法就可以,
      要睜大眼睛並且仔細看清楚喔,很多時候都是類別的方法錯用而產生的錯誤。

      刪除
    2. to mrkt:
      開發環境為 Visual Studio 2012 Professional SP4 + NET MVC 4 + 網際網路應用程式專案 and 基本專案 兩個一起實作
      謝謝前輩指導,再仔細看清楚後,丟回來已經正常。

      刪除
  2. to Kevin Tseng:
    算是MVC初學者練功,把您寫的內容硬是用VB改寫了一次,以下是內容(開發環境:Visual Studio 2012 Professional SP4+MVC 4+ 網際網路應用程式專案 and 基本專案,我的主力是C#)

    【HotSpotController】
    Async Function Index() As Task(Of ActionResult)
    Dim targetURL = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json"
    Dim Client As New HttpClient()
    Client.MaxResponseContentBufferSize = Int32.MaxValue
    Dim response = Await Client.GetStringAsync(targetURL)
    ViewBag.Messenge = response
    Return View()
    End Function

    【檢視】
    @ModelType IEnumerable(Of BlogSample.Models.HotSpot)
    @Code
    ViewData("Title") = "Index"
    End Code

    [h2]新北市 WIFI 熱點[/h2]
    [div class="Well"]@ViewBag.Messenge[div]

    【HotSpot 類別】
    Public Class HotSpot
    Public Property id As String
    Public Property spot_name As String
    Public Property type As String
    Public Property company As String
    Public Property district As String
    Public Property address As String
    Public Property apparatus_name As String
    Public Property latitude As String
    Public Property longitude As String
    End Class

    回覆刪除
    回覆
    1. Hello, 請問這是?
      另外... 我看到 VB.NET 程式是會自動切換為看不懂模式,抱歉。

      刪除
  3. 實作C#後手癢,想看看在VB底下能不能執行

    回覆刪除
    回覆
    1. 相同的邏輯下,只要轉成 VB.NET 的程式沒有太大的問題,應該是可以執行,
      最後都是編譯成電腦看得懂的 Code 在執行。

      刪除
    2. 感謝Kevin Tseng提供學習資源,自己實際寫過才知道差異很多(尤其VB Function抬頭部份,變異非常大)。

      刪除
    3. 其實有些 Visual Studio 的 Extensions 有提供 C#, VB.NET Convert 的功能,不過我沒有用過,因為沒有這樣的需求。
      很多程式真的是要實際動手作過一次才會知道一些細節的地方該註一些什麼。

      刪除
  4. 繼續補完

    【JsonConvert.DeserializeObject() 反序列化 JSON 字串】

    Async Function Index() As Task(Of ActionResult)

    Dim targetURL = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json"
    Dim Client As New HttpClient()
    Client.MaxResponseContentBufferSize = Int32.MaxValue
    Dim response = Await Client.GetStringAsync(targetURL)
    Dim Collection = JsonConvert.DeserializeObject(Of IEnumerable(Of HotSpot))(response)
    ViewBag.Messenge = response
    Return View()

    End Function

    【MSDN - CacheItemPolicy 類別 (System.Runtime.Caching)】

    Private Async Function GetHotSpotData() As Task(Of IEnumerable(Of HotSpot))

    Dim cacheName = "WIFI_HOTSPOT"
    Dim cache As ObjectCache = MemoryCache.Default
    Dim cacheContents As CacheItem = cache.GetCacheItem(cacheName)

    If cacheContents Is Nothing Then
    Return Await RetriveHotSpotData(cacheName)
    Else
    Return TryCast(cacheContents.Value, IEnumerable(Of HotSpot))
    End If

    End Function


    Private Async Function RetriveHotSpotData(cacheName As String) As Task(Of IEnumerable(Of HotSpot))

    Dim targetURL = "http://data.ntpc.gov.tw/NTPC/od/data/api/IMC123/?$format=json"
    Dim client As New HttpClient()
    client.MaxResponseContentBufferSize = Int32.MaxValue
    Dim response = Await client.GetStringAsync(targetURL)
    Dim collection = JsonConvert.DeserializeObject(Of IEnumerable(Of HotSpot))(response)
    Dim policy As New CacheItemPolicy()
    policy.AbsoluteExpiration = DateTime.Now.AddMinutes(30)
    Dim cacheItem As ObjectCache = MemoryCache.[Default]
    cacheItem.Add(cacheName, collection, policy)
    Return collection
    End Function

    【修改後的 Index Action 方法】
    Async Function Index() As Task(Of ActionResult)

    Dim hotSpotSource = Await Me.GetHotSpotData()
    ViewData.Model = hotSpotSource
    Return View()

    End Function

    回覆刪除
  5. 您好
    請問
    當json格式是
    {"ReportTypeQuery":{"ReportGroup":[{"Name":"專家觀點","Count":1,"Type":"U","Report":{"content":"專家觀點","Type":"U001"}},{"Name":"向威觀點","Count":6,"Type":"A","Report":[{"content":"向威投資日誌","Type":"A001"},{"content":"向威全球金融週報","Type":"A002"},{"content":"向威市場評論","Type":"A003"},{"content":"向威市場快訊","Type":"A004"},{"content":"向威投資聚焦","Type":"A005"},{"content":"向威看線論勢","Type":"A006"}]},{"Name":"市場研究","Count":8,"Type":"B","Report":[{"content":"全球","Type":"B001"},{"content":"美國","Type":"B002"},{"content":"歐洲(不含東歐)","Type":"B003"},{"content":"新興市場","Type":"B005"},{"content":"債券市場","Type":"B006"},{"content":"商品市場","Type":"B007"},{"content":"亞洲","Type":"BE004"},{"content":"其他","Type":"BE999"}]}]}}
    是這樣的話,在產稱model會多出現一個class,請問在View跟Model上要怎麼做處理?謝謝

    回覆刪除
    回覆
    1. Hello,
      因為我不清楚你所說的「多一個 class」視什麼樣的情況?所以我無法回答你,
      如果要繼續討論,留言板不適合做討論,可以善加利用左邊的「詢問與建議」功能,

      刪除
  6. 謝謝您所提供的教學,對於剛入門MVC的我受益非淺。
    但在經過實做後,發現在json如果沒有跟政府機關提供的這麼單純的時候
    在controller 裡的 var collection = JsonConvert.DeserializeObject>(response);
    會發生錯誤,經過推測可能是在var response = await client.GetStringAsync(targerURL);再抓取JSON資
    料時,可能是格式方面不對,經過幾天研究還是沒有頭緒,請問前輩指點一下方向,謝謝。
    我的JSON 格式傳回來的樣子是:
    {
    "SearchResult": {
    "FundInfo": [
    {
    "ymdon": "2014/12/31",
    "egi_type": "",
    "egi_area": "",
    "FundName": "全家",
    "ChgValue": -0.11,
    "rr2Y": 58.12,
    "FundCompany": "A0005",
    "rr10Y": "",
    "NavOn": "2015/01/26",
    "rrFromEst": 57.65,
    "rr3M": 64.05,
    "id": 1058,
    "rr1M": 37.21,
    "ChgRate": -0.3571,
    "Isin_code": "",
    "Currency": "新台幣",
    "rr1Y": 72.67,
    "Nav": 30.69,
    "rr6M": 85.04,
    "rr3Y": "",
    "CompanyName": "全家",
    "egi_company": "",
    "rrTY": 72.67,
    "rr5Y": "",
    "Fluc3M": 0.6753
    }
    }

    回覆刪除
    回覆
    1. 以這份 JSON 文件的內容,我毫無頭緒,因為 JSON 就是 JSON,只是一份資料的文件,這份文件並不會告訴你我有什麼問題,因為大部分的問題都會出現在資料的介接、通訊上,又或者是在做反序列化時,無法對映到類別的屬性,這些才是要去觀察的。

      刪除

提醒

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