現在越來越多政府單位有提供公開資料,我們可以拿這些公開資料來做一些工具或是服務,大部分的政府公開資料都會提供 JSON 或是 XML 格式的資料,也有一些是直接提供檔案讓我們下載使用,所以這一次就來練習怎麼在 ASP.NET MVC 網站裡使用政府公開資料,這裡將會使用「新北市政府資料開放平台」的「新北市 WIFI 熱點」來作為這次練習的資料來源。
開發環境:Visual Studio 2013, ASP.NET MVC 5
這裡有提供以 URL 取得 JSON, XML, CSV,還有下載 Excel 檔案的方式,在範例裡將會使用以 URL 取得 JSON 資料的方式。
首先在建立好的空白 ASP.NET MVC 專案裡加入一個 HotSpotController,
接著我們要使用 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 方法也需要修改為非同步。
檢視頁面就很簡單,把取回的 JSON 字串顯示出來,
執行結果
但是這樣的 JSON 字串並不能直接拿來使用,所以要反序列化還原為物件,這邊我們不必自己用手動的方式去建立類別,可以使用「編輯 > 選擇性貼上 > 貼上 JSON 做為類別」的方式來建立,
先複製 JSON 字串裡的一個物件的資料「{"id":"ZZZITWF100261","spot_name":"中和錦和運動公園-司令台 -3","type":"NewTaipei","company":"全球一動","district":"中和區","address":"235新北市中和區錦和路350號","apparatus_name":"新北市中和區公所","latitude":"24.99249","longitude":"121.49033"}」,然後在 Models 目錄裡建立一個 HotSpot 類別的檔案,再來就是使用「編輯 > 選擇性貼上 > 貼上 JSON 做為類別」建立類別,如下:
依據「新北市WIFI熱點」頁面上的欄位定義,稍做修改,
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,
檢視頁面也做了修改,改以 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>
執行結果
但每次要執行時就必須要到遠端取得資料,所以我們可以在第一次取得資料後放到快取裡,之後讀取資料就從快取裡取得,當快取資料的期限過了或是快取資料不見就再到遠端資料來源去拿。
這邊的快取並不是使用 System.Web.Caching 而是使用 System.Runtime.Caching,
MSDN - System.Runtime.Caching 命名空間
要使用 System.Runtime.Caching 要另外加入組件參考,
到指定 URI 位置取得資料並且將資料置入 Cache 裡,快取是放在記憶體中,然後設定快取的期限為 30 分鐘,
MSDN - CacheItemPolicy 類別 (System.Runtime.Caching)
修改後的 Index Action 方法
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)
以上
mrkt:
回覆刪除初訪貴舍,按照您寫的內容實作了一遍,丟回的物件是 "System.Net.Http.StreamContent+ReadOnlyStream" (使用網際網路應用程式專案) 與找不到路徑 (使用基本專案),請問前輩是有那步驟有缺少?
你所提供的訊息並不是能夠讓我做更進一步的分析與釐清,也不曉得你的開發環境為何,
刪除我一開始有提到我所使用的開發環境為「Visual Studio 2013, ASP.NET MVC 5」
另外補充所使用的 .NET Framework 為 4.5.1,然後這一篇的內容並未使用到第三方套件。
我的研判是.... 你用的是 GetStreamAsync 方法,而並不是我範例程式裡所使用的 GetStringAsync 方法,
所以就會出現你所說的丟回來的是「System.Net.Http.StreamContent+ReadOnlyStream」類別的物件,
對方並不是給我們 Stream 資料,而是一般的 JSON 文字內容,所以只要使用 GetStringAsync 方法就可以,
要睜大眼睛並且仔細看清楚喔,很多時候都是類別的方法錯用而產生的錯誤。
to mrkt:
刪除開發環境為 Visual Studio 2012 Professional SP4 + NET MVC 4 + 網際網路應用程式專案 and 基本專案 兩個一起實作
謝謝前輩指導,再仔細看清楚後,丟回來已經正常。
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
Hello, 請問這是?
刪除另外... 我看到 VB.NET 程式是會自動切換為看不懂模式,抱歉。
實作C#後手癢,想看看在VB底下能不能執行
回覆刪除相同的邏輯下,只要轉成 VB.NET 的程式沒有太大的問題,應該是可以執行,
刪除最後都是編譯成電腦看得懂的 Code 在執行。
感謝Kevin Tseng提供學習資源,自己實際寫過才知道差異很多(尤其VB Function抬頭部份,變異非常大)。
刪除其實有些 Visual Studio 的 Extensions 有提供 C#, VB.NET Convert 的功能,不過我沒有用過,因為沒有這樣的需求。
刪除很多程式真的是要實際動手作過一次才會知道一些細節的地方該註一些什麼。
繼續補完
回覆刪除【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
您好
回覆刪除請問
當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上要怎麼做處理?謝謝
Hello,
刪除因為我不清楚你所說的「多一個 class」視什麼樣的情況?所以我無法回答你,
如果要繼續討論,留言板不適合做討論,可以善加利用左邊的「詢問與建議」功能,
謝謝您所提供的教學,對於剛入門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
}
}
以這份 JSON 文件的內容,我毫無頭緒,因為 JSON 就是 JSON,只是一份資料的文件,這份文件並不會告訴你我有什麼問題,因為大部分的問題都會出現在資料的介接、通訊上,又或者是在做反序列化時,無法對映到類別的屬性,這些才是要去觀察的。
刪除