Mask Query API
As these days' situations in the whole world cause by Coronavirus Disease 2019, aka COVID-19, or 武漢肺炎 (Thanks to every Government officials, and other Releated Heroes in Taiwan, did their duty for people's health, and life.). Wearing a mask outside in public place is an important things now.
According to a friend's post: 簡單四步驟:使用ASP.NET Core提供口罩剩餘數量查詢API, we will build a mask query api but something different.
Here we go, let's do it.
Create a Model
First, based on 健保特約機構口罩剩餘數量明細清單, an official open data source from government.
public class MaskInfo
{
/// <summary>
/// 醫事機構代碼
/// </summary>
[Index(0)]
public string Code { get; set; }
/// <summary>
/// 醫事機構名稱
/// </summary>
[Index(1)]
public string Name { get; set; }
/// <summary>
/// 醫事機構地址
/// </summary>
[Index(2)]
public string Address { get; set; }
/// <summary>
/// 醫事機構電話
/// </summary>
[Index(3)]
public string Phone { get; set; }
/// <summary>
/// 成人口罩剩餘數
/// </summary>
[Index(4)]
[JsonPropertyName("adults")]
public int AdultRemainings { get; set; }
/// <summary>
/// 兒童口罩剩餘數
/// </summary>
[Index(5)]
[JsonPropertyName("child")]
public int ChildRemainings { get; set; }
/// <summary>
/// 來源資料時間
/// </summary>
[Index(6)]
[JsonPropertyName("time")]
public string ResourceTime { get; set; }
}
An attribute Index(0) presents. For now, we just ignore that thing, I will explain it later.
Create a Service
Our goal is making a service directly read from the data source of .csv file. Why .csv ? In this case, my consideration of json that is too large to transmit via internet.
A data source of 733KB file (.csv) vs. 10.5MB file (.json), what will you choose ?
So that, we will need CsvHelper to help us to deal with .csv easily.
Things we mentioned earlier about Index(0), these are for CsvHelper models mapper, a model field maps to a specific csv column.
public class MaskService
{
public readonly HttpClient _client;
public MaskService(HttpClient client)
{
client.BaseAddress = new Uri("https://data.nhi.gov.tw/");
this._client = client;
}
public async Task<IEnumerable<MaskInfo>> GetMasks()
{
var resp = await this._client.GetAsync("resource/mask/maskdata.csv").ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
using var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false);
// using var stream = new FileStream(@"x:\maskdata.csv", FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
using var csv = new CsvReader(reader, CultureInfo.CurrentCulture);
csv.Configuration.HasHeaderRecord = true;
csv.Configuration.ShouldSkipRecord = r => r[0] == "醫事機構代碼";
csv.Configuration.IgnoreBlankLines = true;
// codes for model mapping, separating these parts into other class (file) to deal with it will be a good idear.
var masks = new List<MaskInfo>();
while (await csv.ReadAsync().ConfigureAwait(false))
{
csv.TryGetField<string>(0, out var code);
csv.TryGetField<string>(1, out var name);
csv.TryGetField<string>(2, out var addr);
csv.TryGetField<string>(3, out var phone);
csv.TryGetField<int>(4, out var adults);
csv.TryGetField<int>(5, out var childred);
csv.TryGetField<string>(6, out var time);
masks.Add(new MaskInfo
{
Code = code,
Name = name,
Address = addr,
Phone = phone,
AdultRemainings = adults,
ChildRemainings = childred,
ResourceTime = time
});
}
return masks;
// return csv.GetRecords<MaskInfo>();
}
}
According to CsvHelper document, the model mapping block should be completed by one line code,
return csv.GetRecords<MaskInfo>();
Unfortunately, I cannot figure it out that code won't work correctly, 'cause the yield retuen will break after 29 records read. However, we will worry nothing, we still can do model mapping manually.
Register Service
For the api controller calling, we need to register HttpClient in Startup first.
services.AddHttpClient<MaskService>();
It will inject HttpClient instance into MaskService when we need the service.
Implement Api Controller
Final step. We build an api for 3rd party using.
[Route("api/[controller]")]
[ApiController]
public class MaskController : ControllerBase
{
private readonly MaskService _maskService;
public MaskController(MaskService maskService)
{
this._maskService = maskService;
}
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any)]
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var data = await this._maskService.GetMasks().ConfigureAwait(false);
return Ok(data);
}
catch
{
throw;
}
}
}
When api called, we will see the mask info results.
I believe that you noticed a ResponseCache attribute set for the action. It's a good strategy to prevent from people call api frequently.
One thing to go, it's quite easy to build an api service by using ASP.Net core.
Enjoy it.
References:
簡單四步驟:使用ASP.NET Core提供口罩剩餘數量查詢API
簡單五步驟:以EF Core整合SQLite儲存口罩剩餘數量資訊