前言
還記得上一篇文章中所說的配置嗎?本篇文章算是上一篇的延續吧。在 .NET Core 中讀取配置檔案大多數會為配置選項繫結一個POCO(Plain Old CLR Object)物件,並通過依賴注入使用它。而這種使用方式稱之為選項模式。而選項模式使是用類來提供對相關設定組的強型別訪問,同時選項還提供驗證配置資料的機制,是不是很強大,讓我們一點點揭開它的神祕面紗。
ASP.NET Core 6.0 Web API簡要說明
首先開發工具上需要VS2022了,這裡非常推薦大家下載使用,在編碼上真的越來越符合我們開發者的使用了,提示也更加智慧化,VS2022詳細說明,下載以及註冊碼可以參考我的上一篇部落格,這裡就不做詳細說明了VS2022傳送門(含註冊碼)。
.NET 6.0 已經出來有一段時間了,為了跟進技術的進步,筆者後續的系列筆記也將使用.NET 6.0作為目標框架,那麼.NET 6.0 ASP.NET Core Web API 中帶了了哪些變化呢?這裡我做下簡要說明。
- 開啟Visual Studio 2022,建立新專案,選擇“ASP.NET Core Web API”專案模板:
這裡可以看見Use controllers選項預設選中的,取消該選項,則會建立最小Web API。 - 結構變化
這裡可以看下Program.cs檔案的變化
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
可以發現我們熟知的Startup.cs不見了,現在,全部都在Program.cs中實現:
- 在WebApplicationBuilder例項上使用Addxxx方法向DI容器註冊特定服務,類似Startup類的ConfigureServices方法實現。
- 在WebApplication例項上使用Usexxx方法將一系列中介軟體加入到HTTP管道,類似Startup類的Configure方法實現。
- DTO使用record定義,而且也放在同一個Program.cs檔案中。
說明: Visual Studio 2022預設使用 Kestrel Web伺服器,而不是IIS Express。
配置繫結
首先建立一個json格式的配置檔案
{
"JsonConfig": {
"Title": "MyTitle",
"Name": "Tom"
}
}
建立選項類
public class MyOptions
{
public const string JsonConfig = "JsonConfig";
public string? Title { get; set; }
public string? Name { get; set; }
}
選項類說明:
- 必須是包含公共無引數建構函式的非抽象類。
- 型別的所有公共讀寫屬性都已繫結。
- 不會繫結欄位。 在上面的程式碼中,Position 未繫結。 由於使用了 Position 屬性,因此在將類繫結到配置提供程式時,不需要在應用中對字串 "Position" 進行硬編碼。
使用 ConfigurationBinder 繫結
新建一個控制器來讀取配置檔案。
[Route("api/[controller]/[action]")]
[ApiController]
public class OptionsController : ControllerBase
{
private readonly IConfiguration _configuration;
public OptionsController(IConfiguration configuration)
{
_configuration=configuration;
}
public ContentResult GetOptions()
{
var options = new MyOptions();
//方式1
_configuration.GetSection(MyOptions.JsonConfig).Bind(options);
//方式2
_configuration.GetSection(MyOptions.JsonConfig).Get<MyOptions>();
return Content($"Title:{options.Title}\n Name:{options.Name}");
}
}
說明:
這裡的型別繫結有兩種方式分別為ConfigurationBinder.Bind
和 ConfigurationBinder.Get
,
前者相對於後者更方便一些,在開發過程當中比較推薦使用後者做型別繫結。
依賴注入繫結
服務容器中繫結配置。
using OptionsTest.Model;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
//注入繫結
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection(MyOptions.JsonConfig));
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
新建一個控制器來讀取配置檔案。
[Route("api/[controller]/[action]")]
[ApiController]
public class Options2Controller : ControllerBase
{
private readonly MyOptions _options;
public Options2Controller(IOptions<MyOptions> options)
{
_options = options.Value;
}
public ContentResult GetOptions()
{
return Content($"Title:{_options.Title}\n Name:{_options.Name}");
}
}
選項介面
IOptions
- 不支援:
- 在應用啟動後讀取配置資料。
- 命名選項
- 註冊為單一例項且可以注入到任何服務生存期。
IOptionsSnapshot
- 在每次請求時應重新計算選項的方案中有用。 有關詳細資訊,請參閱使用 IOptionsSnapshot 讀取已更新的資料。
- 註冊為範圍內,因此無法注入到單一例項服務。
- 支援命名選項
IOptionsMonitor
- 用於檢索選項並管理 TOptions 例項的選項通知。
- 註冊為單一例項且可以注入到任何服務生存期。
- 支援:
- 更改通知
- 命名選項
- 可過載配置
- 選擇性選項失效 (IOptionsMonitorCache
)
IOptionsSnapshot
在專案中如何在改變配置檔案後,在不重啟專案的前提下自動載入配置檔案呢?IOptionsSnapshot
就支援讀取已更新的配置值。
在前面專案的基礎上略做改變就能做到,MyOptions.cs、MyJsonConfig.json、Program.cs不需要改變。在OptionsController中改用IOptionsSnapshot注入就可以了,程式碼如下:
[Route("api/[controller]/[action]")]
[ApiController]
public class Options2Controller : ControllerBase
{
private readonly MyOptions _options;
public Options2Controller(IOptionsSnapshot<MyOptions> options)
{
_options = options.Value;
}
public ContentResult GetOptions()
{
return Content($"Title:{_options.Title}\n Name:{_options.Name}");
}
}
是不是很簡單,這樣每次改完配置檔案就不需要重啟程式了。
IOptionsMonitor
MyOptions.cs、MyJsonConfig.json、Program.cs不需要改變。在OptionsController中改用IOptionsMonitor注入就可以了,程式碼如下:
[Route("api/[controller]/[action]")]
[ApiController]
public class OptionsController : ControllerBase
{
private readonly MyOptions _options;
public Options2Controller(IOptionsMonitor<MyOptions> options)
{
_options = options.Value;
}
public ContentResult GetOptions()
{
return Content($"Title:{_options.Title}\n Name:{_options.Name}");
}
}
說明:
IOptionsMonitor
是一種單一示例服務,可隨時檢索當前選項值,這在單一例項依賴項中尤其有用。IOptionsSnapshot
是一種作用域服務,並在構造IOptionsSnapshot
物件時提供選項的快照。 選項快照旨在用於暫時性和有作用域的依賴項。
命名選項
命名選項:
- 當多個配置節繫結到同一屬性時有用。
- 區分大小寫。
新建json配置檔案
{
"JsonConfig": {
"Title": "MyTitle",
"Name": "Tom"
},
"JsonConfig2": {
"Title": "MyTitle2",
"Name": "Jerry"
}
}
在屬性相同的情況下,不需要建立兩個類,我們增加一個欄位就可以了。
public class MyOptions
{
public const string JsonConfig = "JsonConfig";
public const string JsonConfig2 = "JsonConfig2";
public string? Title { get; set; }
public string? Name { get; set; }
}
Program.cs中配置命名選項。
using OptionsTest3.Model;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
//注入繫結
builder.Services.Configure<MyOptions>(MyOptions.JsonConfig,builder.Configuration.GetSection(MyOptions.JsonConfig));
builder.Services.Configure<MyOptions>(MyOptions.JsonConfig2, builder.Configuration.GetSection(MyOptions.JsonConfig2));
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
在Controller中讀取配置。
[Route("api/[controller]/[action]")]
[ApiController]
public class Options3Controller : ControllerBase
{
private readonly MyOptions _options;
private readonly MyOptions _options2;
public Options3Controller(IOptionsSnapshot<MyOptions> options)
{
_options = options.Get(MyOptions.JsonConfig);
_options2 = options.Get(MyOptions.JsonConfig2);
}
public ContentResult GetOptions()
{
return Content($"Title:{_options.Title}\n Name:{_options.Name}\n" +
$"Title:{_options2.Title}\n Name:{_options2.Name}");
}
}
選項驗證
在配置檔案中對於配置項的驗證是必不可少的。這樣可以保證程式配置的正確性。具體如何實現呢,且聽筆者慢慢道來。
首先建立一個簡單的Json配置檔案。
{
"JsonConfig": {
"ID": 10000000,
"Title": "MyTitle",
"Name": "Tom"
}
}
建立我們配置項的驗證Model。
public class MyOptions
{
public const string JsonConfig = "JsonConfig";
[Range(0, 1000,
ErrorMessage = "值 {0}必須在 {1} 和 {2}之間。")]
public int ID { get; set; }
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string? Title { get; set; }
public string? Name { get; set; }
}
在Program.cs中配置我們的選項和驗證。
using OptionValidtate.Model;
using System.Configuration;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
builder.Services.AddOptions<MyOptions>().Bind(builder.Configuration.GetSection(MyOptions.JsonConfig)).ValidateDataAnnotations();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
說明:注意到和之前的程式碼有什麼不同了嗎?在我們正是通過呼叫ValidateDataAnnotations
以使用 DataAnnotations 來完成驗證。
在我們的Controller中讀取我們的配置。這裡用到了ILogger,可以先不用關心,在後續筆者會詳細講解。
[Route("api/[controller]/[action]")]
[ApiController]
public class OptionsController : ControllerBase
{
private readonly ILogger<OptionsController> _logger;
private readonly IOptions<MyOptions> _options;
public OptionsController(IOptions<MyOptions> options, ILogger<OptionsController> logger)
{
_options = options;
_logger = logger;
try
{
var optionsValue = _options.Value;
}
catch (OptionsValidationException e)
{
foreach (var failure in e.Failures)
{
_logger.LogError(failure);
}
}
}
public ContentResult GetOptions()
{
string msg;
try
{
msg=$"ID:{_options.Value.ID}\nTitle:{_options.Value.Title}\nName:{_options.Value.Name}\n";
}
catch (OptionsValidationException e)
{
return Content(e.Message);
}
return Content(msg);
}
}
還記得我們們配置的ID驗證規則嗎?啟動程式看下我們的驗證已經生效了。
IValidateOptions複雜驗證的實現
如果上面的驗證還不滿足要求的話,沒關係我對上面的程式碼略作修改,通過IValidateOptions
就可以實現更加的複雜驗證。
新建MyValidateOptions類繼承IValidateOptions介面並實現。
public class MyValidateOptions : IValidateOptions<MyOptions>
{
public MyOptions _options { get; private set; }
public MyValidateOptions(IConfiguration config)
{
_options = config.GetSection(MyOptions.JsonConfig).Get<MyOptions>();
}
public ValidateOptionsResult Validate(string name, MyOptions options)
{
string msg = null;
var rxValidate = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
if (options.ID<0||options.ID>1000)
{
msg = $"{options.ID}必須在0-1000之間。";
}
if (string.IsNullOrEmpty(options.Title))
{
msg = $"{options.Title}必須符合正則要求。";
}
if (msg !=null)
{
return ValidateOptionsResult.Fail(msg);
}
return ValidateOptionsResult.Success;
}
}
在Program.cs中配置我們的選項和驗證。
using Microsoft.Extensions.Options;
using OptionValidtate.Model;
using System.Configuration;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
builder.Services.AddOptions<MyOptions>().Bind(builder.Configuration.GetSection(MyOptions.JsonConfig));
builder.Services.AddSingleton<IValidateOptions<MyOptions>,MyValidateOptions>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
好了配置完畢,執行程式可以看到我們配置的驗證已經生效了。