ASP.NET core 的名字還沒記熟它又要改版本名了,不過對未來的發展這或許是好事吧。依據 roadmap 所示未來的版本名稱就會比較清楚一點。而在 .net 發長趨於穩定時開發應用時讓你又愛又恨的文件撰寫,你還在獨立寫一份嗎?程式碼跟文件之間的更新成本就不能在壓低一點嗎?若你還沒聽過 NSwag 的話這是一個你值得做的嘗試,另外不可或缺的就是 log 系統,你還在用 NLog 或是內建的日誌 lib 嗎?不體驗一下 Serilog 就太可惜了,尤其是目前應用多數跑在 container 裡,在系統 micro-service 化的現代裡,將 log 寫在本機裡在 container 數量漸多後管理上是有點麻煩,而 serilog 的外掛可以幫你把 log 直接送去指定的地方。
本文將會基於 Asp.NET Core 3.1 版本來開發,雖然 .NET 5 已經推出但是它並非 LTS 版本所以並非首選。
本文的 source code 在 這裡
第一個步驟就是把必要的 Packages 裝一裝
1
| dotnet add .\AspNetCoreSerilog\AspNetCoreSerilog.csproj package Serilog.AspNetCore
|
在 appsetting.json
直接將原本的 Logging
區塊取代為以下設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| {
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "AspNetCoreSerilog"
}
}
}
|
而在 Program.cs
也就是入口程式那邊補上以下程式
1
2
3
4
5
6
7
8
9
10
| // 抓取 serilog 設定檔的內容
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", true)
.Build();
// 讓 serilog 使用設定檔的內容來運作
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
|
這樣就是一個基本的 serilog 設定完成了,接下來安裝 NSwag 的相關套件
1
| dotnet add .\AspNetCoreSerilog\AspNetCoreSerilog.csproj package NSwag.AspNetCore
|
接下來進行基本的設定就 NSwag 就算成功在系統上 run 起來了
1
2
3
4
5
6
7
8
9
10
11
| public void ConfigureServices(IServiceCollection services)
{
// 加上以下這一段設定
services.AddOpenApiDocument(
document =>
{
document.Title = "AspNetCore NSwag";
document.Description = "Demo for NSwag on ASP.NET Core";
document.DocumentName = "v1";// 預設值為 v1
});
}
|
1
2
3
4
5
| public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 註冊 OpenApi 文件產生的 middleware
app.UseOpenApi();
}
|
在設定並註冊完上述的設定後,可以在 local run 起來以下的位置,就可以看到 NSwag 把你該系統的 API 都抓出來了
curl -X GET "{FQDN}/swagger/v1/swagger.json" -H "accept: application/json"
實際看過這份文件之後應該跟你想像中好文件的差距頗大,它需要更炫的 UI !而這當然也有 package 可以幫我們。很幸運的是這個 package 其實就是整在我們剛剛裝的那個 package 裡,所以設定也只需要多加一行
1
2
3
4
5
| public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 註冊 Swagger UI
app.UseSwaggerUi3();
}
|
curl -X GET "{FQDN}/swagger" -H "accept: application/json"
這樣就可以了,也會是最多人在被推薦這套 package 時最常看到的模樣,不過看起來他只是幫你把你的 API 列出來並且有一個介面可以打,目前看起來還是比較像是本機的高級除錯工具,而跟線上文件比較無關,接下來就是要讓 Swagger 可以吃定義在 Model 身上的描述。
1
2
3
4
5
6
7
8
9
| [HttpGet("{days}")]
[OpenApiOperation("This is operation summary","This is operation description")]
[ProducesResponseType(typeof(WeatherForecast), 200)]
public IEnumerable<WeatherForecast> Get(
[Required][FromRoute][Description("days that will show")]
int days)
{
// some logic in controller
}
|
主要標籤 OpenApiOperation Source Code
我們在 Action 身上補上 OpenApiOperation
標籤,這個標籤所接受的兩個參數就會是 Swagger 介面中我們會看到的 Api 概述與詳述。
而參數的部分可以使用既有的 ComponentModel attribute: DescriptionAttribute
就可以讓 Swagger 吃到,這樣就可以了。
不過很多情況下 API 會要先通過一些授權機制才能正常呼叫,而 Swagger 也需要做點調整才能正常呼叫。以下是目前 Swagger 支援的方式:
- API key
- HTTP
- OAuth 2.0
- Open ID Connect
這邊就用最常見的 JWT 授權方式來演示一下作法,在原本的 Startup.cs
裡的 void ConfigureServices(IServiceCollection services)
裡的 AddOpenApiDocument
中插入以下片段
1
2
3
4
5
6
7
8
9
10
11
| var apiScheme = new OpenApiSecurityScheme()
{
Type = OpenApiSecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
Description = "JWT Token Auth"
};
// empty string array 這邊直接指明 new string[]{"Bearer"} 也是可以的
config.AddSecurity("Bearer", Enumerable.Empty<string>(), apiScheme);
config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor());
|
這樣補上去之後 Swagger 在發送 request 時就會補上 Authoriztion 為 Bearer 的 header。
這邊我們先實做一個自己的 Auth 機制,為求簡單裡面沒有真的檢查任何東西
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Net.Http.Headers;
using System;
using System.Net.Http.Headers;
using System.Threading.Tasks;
// Attribute, 若不是全域註冊的話會需要繼承
public class MyAuth : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var authorization = context.HttpContext.Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var authValue))
{
if(string.Equals(authValue.Scheme, JwtBearerDefaults.AuthenticationScheme, StringComparison.OrdinalIgnoreCase))
{
return;
}
}
context.Result = new ForbidResult(JwtBearerDefaults.AuthenticationScheme);
}
}
|
然後在 Startup.cs
中將這個 Auth 註冊進去,這邊採用的註冊方式是全域註冊,若要 by controller or action 的話就要繼承 Attribute
。
1
2
3
4
5
6
7
8
9
10
11
12
13
| public void ConfigureServices(IServiceCollection services)
{
//...其他的註冊
// Add this when you use Jwt Bearer in asp.net core
services.AddAuthentication().AddJwtBearer();
services.AddAuthorization();
// Add MyAuth as a global auth filter
services.AddControllers(option =>
{
option.Filters.Add(typeof(MyAuth));
});
//...其他的註冊
}
|
以上大概就是基本的 NSwag 的一些設定,若提供的 API 是內部在呼叫的話,其實這些設定應該就會滿夠用的。
Reference#