ASP.NET Core 2 使用 HttpClientFactory 讀取中央氣象局 OpenData 的XML 下載
這是使用 ASP.NET Core 2 來讀取 CWB 的開放資料的 XML 檔,並讓 Client 端下載方式
註冊CWB會員
有了上述資訊後,就可以著手開始做 .NET Core 2 的取用程式了。
CWODP取用 XML 檔案的 URI:
.NET Core 2 使用 HttpClient 有以下幾種:
一般 HttpClient 方式
FileController.cs
上面這段是展示如何使用標準 HttpClient 向 CWBODP 取得XML檔案,並顯示在畫面上。
但是如果要變成可以讓使用者端下載,就必須取得原本的檔案名稱資訊,並將返回資訊改成 FileResult 型別返回。如下:
FileController.cs
上面標準方式使用起來沒有問題,使用人數少的情況下也都還好,但是在很多人使用情況下下 HttpClient 就會被反覆的建立/銷毀,這個過程不但浪費時間也消耗資源,而且最嚴重的問題是使用 using 套接 HttpClient 實作時,離開 using 區段後 HttpClient 也沒有被及時銷毀,多人重複使用會導致通訊端口被占用到資源耗盡為止。
第二個問題是 HttpClient 不會理會 DNS 的有效時間 (TTL),可能發生了 DNS 變更了 IP 內容,卻還是繼續使用舊的IP對應。
可參考下面文章:
HttpClientFactory
於是乎,ASP.NET Core 2.1 以後推出了 HttpClientFactory ,它用來集中管理使用的 HttpClient ,這樣可以有效減少重構 HttpClient ,也可以減少資源浪費,重點也解決 DNS 本來就有 TTL 的問題。
直接使用 HttpClientFactory
要使用 HttpClientFactory 方法首先作成 DI 服務
startup.cs
然後,在使用時取用該服務
CWBController .cs
具名式 Client
在建立 DI 時就賦予特定名稱,然後取用時,只要指定名稱就可以取用套好的內容,通常是經常性且固定的取用會很方便。
startup.cs
然後取用具名服務
CWBController .cs
型別式 Client
這種方式好處是可以先定義好很多不同需求的 Client 類別,灌入 DI 後,以Client形式來取用
CWBClient.cs
在 startup.cs 注入 DI 服務
然後取用
CWB2Controller.cs
產生式 Client
這種方式可以封裝整個 HttpClient ,也可以由第三方套件來提供,只要有套件方的介面即可實現。因為此處我沒有第三方套件,所以先由自己做。
首先產生一個介面
ICWBClient.cs
然後建立一個具有介面特徵的類別(被封裝者)
CWBHubClient.cs
注入DI
startup.cs
呼叫引用
CWB3Controller.cs
將 Http 服務封裝注入 DI 好處是除了這些控制器可以使用外,也可以用在 Razor 網頁上,提供不少便利。
參考相關:
註冊CWB會員
- CWB OpenData 平台(以下稱CWBODP)取用資料必須先註冊會員→ 這裡註冊
- 然後,登入OpenData平台→這裡登入
- 選擇『資料選擇說明』頁面,在下方可以找到『取得授權碼』,這個頁面下有使用教學
- 要取得資料代碼可以從『資料清單』頁面取得需要的代碼
有了上述資訊後,就可以著手開始做 .NET Core 2 的取用程式了。
CWODP取用 XML 檔案的 URI:
http://opendata.cwb.gov.tw/opendataapi?dataid={資料代碼}&authorizationkey={授權碼}
.NET Core 2 使用 HttpClient 有以下幾種:
- 一般 HttpClient 方式
- 直接使用 HttpClientFactory
- 具名式 Client
- 型別式 Client
- 產生式 Client
一般 HttpClient 方式
FileController.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
namespace CWBReader.Controllers
{
[Route("[controller]")]
public class FileController : Controller
{
[Route("")]
public async Task<string> DownloadFile()
{
using (var client = new HttpClient())
{
string url = "http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678";
using (var result = await client.GetAsync(url))
{
if (result.IsSuccessStatusCode)
{
//直接將XML內容顯示出來
return await result.Content.ReadAsStringAsync();
}
}
}
throw new Exception("File/can not show XML data!");
}
}
}
上面這段是展示如何使用標準 HttpClient 向 CWBODP 取得XML檔案,並顯示在畫面上。
但是如果要變成可以讓使用者端下載,就必須取得原本的檔案名稱資訊,並將返回資訊改成 FileResult 型別返回。如下:
FileController.cs
namespace CWBReader.Controllers
{
[Route("download")]
public async Task<FileResult> DownFile()
{
using (var client = new HttpClient())
{
string url = "http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678";
using (var result = await client.GetAsync(url))
{
if (result.IsSuccessStatusCode)
{
//以檔案下載方式輸出,取得原檔案名,並改以FileResult輸出
string filename = result.Content.Headers.ContentDisposition.FileName;
byte[] xml = await result.Content.ReadAsByteArrayAsync();
return File(xml, "application/xml", filename);
}
}
}
throw new Exception("File/download can not download file!");
}
}
}
上面標準方式使用起來沒有問題,使用人數少的情況下也都還好,但是在很多人使用情況下下 HttpClient 就會被反覆的建立/銷毀,這個過程不但浪費時間也消耗資源,而且最嚴重的問題是使用 using 套接 HttpClient 實作時,離開 using 區段後 HttpClient 也沒有被及時銷毀,多人重複使用會導致通訊端口被占用到資源耗盡為止。
第二個問題是 HttpClient 不會理會 DNS 的有效時間 (TTL),可能發生了 DNS 變更了 IP 內容,卻還是繼續使用舊的IP對應。
可參考下面文章:
HttpClientFactory
於是乎,ASP.NET Core 2.1 以後推出了 HttpClientFactory ,它用來集中管理使用的 HttpClient ,這樣可以有效減少重構 HttpClient ,也可以減少資源浪費,重點也解決 DNS 本來就有 TTL 的問題。
直接使用 HttpClientFactory
要使用 HttpClientFactory 方法首先作成 DI 服務
startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 使用HttpClientFactory
services.AddHttpClient();
services.AddMvc();
}
然後,在使用時取用該服務
CWBController .cs
namespace CWBReader.Controllers
{
[Route("[controller]")]
public class CWBController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
public CWBController(IHttpClientFactory httpClientFactory)
{
// 取用HttpClientFactory 的服務
_httpClientFactory = httpClientFactory;
}
[Route("t1")]
public async Task<FileResult> GetByFctory1()
{
HttpClient client = _httpClientFactory.CreateClient();
string uri = "http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678";
using (var result = await client.GetAsync(uri))
{
if (result.IsSuccessStatusCode)
{
// 取得XML內容
byte[] content = await result.Content.ReadAsByteArrayAsync();
// 取得下載檔案名稱
string filename = result.Content.Headers.ContentDisposition.FileName;
FileContentResult response = File(content, "application/xml", filename);
return response;
}
}
throw new Exception("CWB/t1 can not download file!");
}
}
}
具名式 Client
在建立 DI 時就賦予特定名稱,然後取用時,只要指定名稱就可以取用套好的內容,通常是經常性且固定的取用會很方便。
startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 使用固定內容,並賦予指定名稱 CWB
services.AddHttpClient("CWB", c =>
{
c.BaseAddress = new Uri("http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678");
c.DefaultRequestHeaders.Add("Accept", "*/*");
c.DefaultRequestHeaders.Add("User-Agent" , "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1");
});
services.AddMvc();
}
然後取用具名服務
CWBController .cs
namespace CWBReader.Controllers
{
[Route("[controller]")]
public class CWBController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
public CWBController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[Route("t2")]
public async Task<FileResult> GetByFctory2()
{
// 在這裡取用具名 CWB
HttpClient client = _httpClientFactory.CreateClient("CWB");
using (var result = await client.GetAsync(""))
{
if (result.IsSuccessStatusCode)
{
byte[] content = await result.Content.ReadAsByteArrayAsync();
string filename = result.Content.Headers.ContentDisposition.FileName;
FileContentResult response = File(content, "application/xml", filename);
return response;
}
}
throw new Exception("CWB/t2 下載不到檔案");
}
}
}
型別式 Client
這種方式好處是可以先定義好很多不同需求的 Client 類別,灌入 DI 後,以Client形式來取用
CWBClient.cs
using System;
using System.Net.Http;
namespace CWBReader.Clients
{
public class CWBClient
{
public HttpClient Client { get; private set; }
public CWBClient(HttpClient client)
{
client.BaseAddress = new Uri("http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1");
this.Client = client;
}
}
}
在 startup.cs 注入 DI 服務
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<CWBClient>();
services.AddMvc();
}
然後取用
CWB2Controller.cs
namespace CWBReader.Controllers
{
[Route("[controller]")]
public class CWB2Controller : Controller
{
private readonly CWBClient _cwbClient;
public CWB2Controller(CWBClient cwbClient)
{
// 取用定義好的 Client
_cwbClient = cwbClient;
}
[Route("t1")]
public async Task<FileResult> GetByClient1()
{
using (var result = await _cwbClient.Client.GetAsync(""))
{
if (result.IsSuccessStatusCode)
{
byte[] content = await result.Content.ReadAsByteArrayAsync();
string filename = result.Content.Headers.ContentDisposition.FileName;
FileContentResult response = File(content, "application/xml", filename);
return response;
}
}
throw new Exception("CWB2/t1 can not download file");
}
}
}
產生式 Client
這種方式可以封裝整個 HttpClient ,也可以由第三方套件來提供,只要有套件方的介面即可實現。因為此處我沒有第三方套件,所以先由自己做。
首先產生一個介面
ICWBClient.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace CWBReader.Clients
{
public interface ICWBClient
{
Task<FileResult> GetFile();
}
}
然後建立一個具有介面特徵的類別(被封裝者)
CWBHubClient.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace CWBReader.Clients
{
public class CWBHubClient :ICWBClient
{
private readonly HttpClient _client;
public CWBHubClient(HttpClient client)
{
client.BaseAddress = new Uri("http://opendata.cwb.gov.tw/opendataapi?dataid=F-C0032-001&authorizationkey=CWB-1234ABCD-78EF-GH90-12XY-IJKL12345678");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1");
_client = client;
}
public async Task<FileResult> GetFile()
{
using (var result = await _client.GetAsync(""))
{
if (result.IsSuccessStatusCode)
{
byte[] content = await result.Content.ReadAsByteArrayAsync();
string filename = result.Content.Headers.ContentDisposition.FileName;
FileContentResult response = new FileContentResult(content, "application/xml") { FileDownloadName = filename };
return response;
}
}
throw new Exception("CWB2/t2 下載不到檔案");
}
}
}
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<ICWBClient, CWBHubClient>();
services.AddMvc();
}
呼叫引用
CWB3Controller.cs
namespace CWBReader.Controllers
{
[Route("[controller]")]
public class CWB3Controller : Controller
{
private readonly ICWBClient _cwbClient;
public CWB3Controller(ICWBClient cwbClient)
{
_cwbClient = cwbClient;
}
[Route("t1")]
public Task<FileResult> GetByClient1()
{
return _cwbClient.GetFile();
}
}
}
將 Http 服務封裝注入 DI 好處是除了這些控制器可以使用外,也可以用在 Razor 網頁上,提供不少便利。
參考相關:
留言