前言
還記得上一篇文章中所說的配置嗎?本篇文章算是上一篇的延續吧。在 .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();
好了配置完畢,運行程序可以看到我們配置的驗證已經生效了。