為ASP.NET Core強(qiáng)類(lèi)型配置對(duì)象添加驗(yàn)證的方法
前言
本篇博客中,我將描述如何在ASP.NET Core程序啟動(dòng)時(shí),確保強(qiáng)類(lèi)型配置對(duì)象正確的綁定成功。通過(guò)使用IStartupFilter接口對(duì)象,你可以更早的驗(yàn)證你的配置對(duì)象是否綁定了正確的值,并不需要等待程序啟動(dòng)之后的某個(gè)時(shí)間點(diǎn)再驗(yàn)證。
這里我將簡(jiǎn)單描述一下ASP.NET Core的配置系統(tǒng),以及如何使用強(qiáng)類(lèi)型配置。我將主要描述一下如何去除對(duì)IOptions接口的依賴(lài),然后我會(huì)描述一下強(qiáng)類(lèi)型配置對(duì)象綁定不正確的問(wèn)題。最后,我將給出一個(gè)在程序啟動(dòng)時(shí)驗(yàn)證強(qiáng)類(lèi)型配置對(duì)象的方案。
ASP.NET Core中的強(qiáng)類(lèi)型配置
ASP.NET Core的配置系統(tǒng)非常的靈活,它允許你從多種數(shù)據(jù)源中讀取配置信息,例如Json文件,YAML文件,環(huán)境變量,Azure Key Vault等。官方推薦方案是使用強(qiáng)類(lèi)型配置來(lái)獲取IConfiguration接口對(duì)象的值。
強(qiáng)類(lèi)型配置使用POCO對(duì)象來(lái)呈現(xiàn)你的程序配置的一個(gè)子集,這與IConfiguration接口對(duì)象存儲(chǔ)的原始鍵值對(duì)不同。例如,現(xiàn)在你正在你的程序中集成Slack, 并且使用Web hooks向頻道中發(fā)送消息,你需要配置Web hook的URL, 以及一些其他的配置。
public class SlackApiSettings
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
public bool ShouldNotify { get; set; }
}
你可以在Startup類(lèi)中使用擴(kuò)展方法Configure,將強(qiáng)類(lèi)型配置對(duì)象和你程序配置綁定起來(lái)。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
當(dāng)你需要讀取配置的時(shí)候,你只需要在你當(dāng)前方法所在類(lèi)的構(gòu)造函數(shù)中注入一個(gè)IOptions接口對(duì)象,即可使用這個(gè)對(duì)象的Value屬性,獲取到配置的值, 這里ASP.NET Core配置系統(tǒng)自動(dòng)幫你完成了強(qiáng)類(lèi)型對(duì)象和配置之間的綁定。
public class TestController : Controller
{
private readonly SlackApiSettings _slackApiSettings;
public TestController(IOptions<SlackApiSettings> options)
{
_slackApiSettings = options.Value
}
public object Get()
{
return _slackApiSettings;
}
}
解除對(duì)IOptions接口的依賴(lài)
可能有些人和我一樣,不太喜歡讓自己創(chuàng)建的類(lèi)依賴(lài)于IOptions接口,我們只希望自己創(chuàng)建的類(lèi)僅依賴(lài)于配置對(duì)象。這里你可以使用如下所述的方法來(lái)解除對(duì)IOptions接口的依賴(lài)。這里我們可以在依賴(lài)注入容器中顯式的注冊(cè)一個(gè)SlackApiSetting配置對(duì)象,并將解析它的方法委托給一個(gè)IOptions對(duì)象
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
services.AddSingleton(resolver =>
resolver.GetRequiredService<IOptions<SlackApiSettings>>().Value);
}
現(xiàn)在你可以在不引用Microsoft.Extensions.Options程序集的情況下,注入了一個(gè)“原始”的配置對(duì)象了。
public class TestController : Controller
{
private readonly SlackApiSettings _slackApiSettings;
public TestController(SlackApiSettings settings)
{
_slackApiSettings = settings;
}
public object Get()
{
return _slackApiSettings;
}
}
這個(gè)解決方案通常都非常有效, 但是如果配置出現(xiàn)問(wèn)題,例如在JSON文件中出現(xiàn)了錯(cuò)誤拼寫(xiě),這里會(huì)發(fā)生什么事情呢?
如果綁定失敗,程序會(huì)發(fā)生什么事情?
我們綁定強(qiáng)類(lèi)型配置對(duì)象的時(shí)候有以下幾種錯(cuò)誤的可能。
節(jié)點(diǎn)名稱(chēng)拼寫(xiě)錯(cuò)誤
當(dāng)你綁定配置的時(shí)候,你需要顯式的指定綁定的配置節(jié)點(diǎn)名稱(chēng),如果你當(dāng)前使用的appsetting.json作為配置文件,json文件中的key即是配置的節(jié)點(diǎn)名稱(chēng)。例如下面代碼中的"Logging"和“SlackApi”
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"SlackApi": {
"WebhookUrl": "http://example.com/test/url",
"DisplayName": "My fancy bot",
"ShouldNotify": true
}
}
為了綁定"SlackApi"節(jié)點(diǎn)的值到強(qiáng)類(lèi)型配置對(duì)象SlackApiSetting, 你需要調(diào)用一下代碼
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
這時(shí)候,假設(shè)我們將appsettings.json中的"SlackApi"錯(cuò)誤的拼寫(xiě)為"SackApi"?,F(xiàn)在我們?nèi)フ{(diào)用前面例子中的TestController中的GET方法,會(huì)得到一下結(jié)果
{
"webhookUrl":null,
"displayName":null,
"shouldNotify":false
}
所有的key都是綁定了他們的默認(rèn)值,但是沒(méi)有發(fā)生任何錯(cuò)誤,這意味著他們綁定到了一個(gè)空的配置節(jié)點(diǎn)上。這看起來(lái)非常糟糕,因?yàn)槟愕拇a并沒(méi)有驗(yàn)證webhookUrl是否是一個(gè)合法的Url。
屬性名拼寫(xiě)錯(cuò)誤
相似的,有時(shí)候拼寫(xiě)的節(jié)點(diǎn)名稱(chēng)正確,但是屬性名稱(chēng)可能拼寫(xiě)錯(cuò)誤。例如, 我們將appSettings.json文件中的"WebhookUrl"錯(cuò)誤的拼寫(xiě)為"Url"。這時(shí)我們調(diào)用前面例子中的TestController中的GET方法,會(huì)得到以下結(jié)果
{
"webhookUrl":null,
"displayName":"My fancy bot",
"shouldNotify":true
}
強(qiáng)類(lèi)型配置類(lèi)的屬性缺少SET訪問(wèn)器
我經(jīng)常發(fā)現(xiàn)一些初級(jí)程序員會(huì)遇到這個(gè)問(wèn)題,針對(duì)屬性,他們只提供了GET訪問(wèn)器,而缺少SET訪問(wèn)器,在這種情況下強(qiáng)類(lèi)型配置對(duì)象是不會(huì)正確綁定的。
public class SlackApiSettings
{
public string WebhookUrl { get; }
public string DisplayName { get; }
public bool ShouldNotify { get; }
}
現(xiàn)在我們?nèi)フ{(diào)用前面例子中的TestController中的GET方法,會(huì)得到以下結(jié)果
{
"webhookUrl":null,
"displayName":null,
"shouldNotify":false
}
不兼容的類(lèi)型值
最后一種情況就是將一個(gè)不兼容的類(lèi)型值,綁定到屬性上。在配置文件中,所有的配置都是以文本形式保存的,但是綁定器需要將他們轉(zhuǎn)換成.NET中支持的基礎(chǔ)類(lèi)型。例如ShouldNotify屬性是一個(gè)布爾類(lèi)型的值,我們只能將"True", "False"字符串綁定到這個(gè)值上,但是如果你在配置文件中,設(shè)置該屬性的值為"THE VALUE", 當(dāng)程序訪問(wèn)TestController時(shí),程序就會(huì)報(bào)錯(cuò)

使用IStartupFilter創(chuàng)建一個(gè)配置驗(yàn)證
為了解決這個(gè)問(wèn)題,我將使用IStartupFilter創(chuàng)建一個(gè)在應(yīng)用啟動(dòng)時(shí)運(yùn)行的簡(jiǎn)單驗(yàn)證步驟,以確保你的設(shè)置正確無(wú)誤。
IStartupFilter接口允許你通過(guò)向依賴(lài)注入容器添加服務(wù)來(lái)間接控制中間件管道。 ASP.NET Core框架使用它來(lái)執(zhí)行諸如“將IIS中間件添加到應(yīng)用程序的中間件管道的開(kāi)頭, 或添加診斷中間件之類(lèi)”的操作。
雖然IStartupFilter經(jīng)常用來(lái)向管道中添加中間件,但是我們也可以不這么做。相反的,我們可以在程序啟動(dòng)時(shí)(服務(wù)配置完成之后,處理請(qǐng)求之前),使用它來(lái)執(zhí)行一些簡(jiǎn)單的代碼。
這里首先我們創(chuàng)建一個(gè)簡(jiǎn)單的接口,強(qiáng)類(lèi)型配置類(lèi)可以通過(guò)實(shí)現(xiàn)這個(gè)接口來(lái)完成一些必要的驗(yàn)證。
public interface IValidatable
{
void Validate();
}
下一步,我們創(chuàng)建一個(gè)SettingValidationStartupFilter類(lèi), 它實(shí)現(xiàn)了IStartupFilter接口
public class SettingValidationStartupFilter : IStartupFilter
{
readonly IEnumerable<IValidatable> _validatableObjects;
public SettingValidationStartupFilter(IEnumerable<IValidatable> validatableObjects)
{
_validatableObjects = validatableObjects;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
foreach (var validatableObject in _validatableObjects)
{
validatableObject.Validate();
}
return next;
}
}
在構(gòu)造函數(shù)中,我們從依賴(lài)注入容器中取出了所有實(shí)現(xiàn)IValidatable接口的強(qiáng)類(lèi)型配置對(duì)象,并在Configure方法中依次調(diào)用他們的Validate方法。
SettingValidationStartupFilter并沒(méi)有修改任何中間件管道, Configure方法中直接返回了next對(duì)象。但是如果某個(gè)強(qiáng)類(lèi)型配置類(lèi)的驗(yàn)證失敗,在程序啟動(dòng)時(shí),就會(huì)拋出異常,從而阻止了程序。
接下來(lái)我們需要在Startup類(lèi)中注冊(cè)我們創(chuàng)建的服務(wù)SettingValidationStartupFilter
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IStartupFilter, SettingValidationStartupFilter>()
// 其他配置
}
最后你需要讓你的配置類(lèi)實(shí)現(xiàn)IValidatable接口, 我們以SlackApiSettings為例,這里我們需要驗(yàn)證WebhoolUrl和DisplayName屬性是否綁定成功,并且我們還需要驗(yàn)證 WebhoolUrl是否是一個(gè)合法的Url。
public class SlackApiSettings : IValidatable
{
public string WebhookUrl { get; set; }
public string DisplayName { get; set; }
public bool ShouldNotify { get; set; }
public void Validate()
{
if (string.IsNullOrEmpty(WebhookUrl))
{
throw new Exception("SlackApiSettings.WebhookUrl must not be null or empty");
}
if (string.IsNullOrEmpty(DisplayName))
{
throw new Exception("SlackApiSettings.WebhookUrl must not be null or empty");
}
// 如果不是合法的Url,就會(huì)拋出異常
var uri = new Uri(WebhookUrl);
}
}
當(dāng)然我們還可以使用DataAnnotationsAttribute來(lái)實(shí)現(xiàn)上述驗(yàn)證。
public class SlackApiSettings : IValidatable
{
[Required, Url]
public string WebhookUrl { get; set; }
[Required]
public string DisplayName { get; set; }
public bool ShouldNotify { get; set; }
public void Validate()
{
Validator.ValidateObject(this,
new ValidationContext(this),
validateAllProperties: true);
}
}
無(wú)論你使用哪一種方式,如果綁定出現(xiàn)問(wèn)題,程序啟動(dòng)時(shí)都會(huì)拋出異常。
最后一步,我們需要將SlackApiSettings 以IValidatable接口的形式注冊(cè)到依賴(lài)注入容器中,這里我們同樣可以使用前文的方法解除對(duì)IOptions接口的依賴(lài)。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<IStartupFilter, SettingValidationStartupFilter>()
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
services.AddSingleton(resolver =>
resolver.GetRequiredService<IOptions<SlackApiSettings>>().Value);
services.AddSingleton<IValidatable>(resolver =>
resolver.GetRequiredService<IOptions<SlackApiSettings>>().Value);
}
測(cè)試結(jié)果
我們可以任選之前列舉的一個(gè)錯(cuò)誤方式來(lái)進(jìn)行測(cè)試,例如,我們將WebhookUrl錯(cuò)誤的拼寫(xiě)為Url。 當(dāng)程序啟動(dòng)時(shí),就會(huì)拋出以下異常。
原文: Adding validation to strongly typed configuration objects in ASP.NET Core
作者: Andrew Lock
譯文: Lamond Lu
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
gridview實(shí)現(xiàn)服務(wù)器端和客戶(hù)端全選的兩種方法分享
這篇文章主要介紹了gridview實(shí)現(xiàn)服務(wù)器端和客戶(hù)端全選的兩種方法,需要的朋友可以參考下2014-02-02
淺談從ASP.NET Core2.2到3.0你可能會(huì)遇到這些問(wèn)題
這篇文章主要介紹了ASP.NET Core2.2到3.0可能會(huì)遇到的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
C#隨機(jī)生成不重復(fù)字符串的兩個(gè)不錯(cuò)方法
發(fā)現(xiàn)兩個(gè)隨機(jī)生成不重復(fù)字符串的方法,感覺(jué)還不錯(cuò),下面與大家分享下2014-05-05
.Net下二進(jìn)制形式的文件(圖片)的存儲(chǔ)與讀取詳細(xì)解析
以下是對(duì).Net下二進(jìn)制形式的文件(圖片)的存儲(chǔ)與讀取進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09
Razor TagHelper實(shí)現(xiàn)Markdown轉(zhuǎn)HTML的方法
下面小編就為大家分享一篇Razor TagHelper實(shí)現(xiàn)Markdown轉(zhuǎn)HTML的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
ASP.NET Core3.1 Ocelot認(rèn)證的實(shí)現(xiàn)
這篇文章主要介紹了ASP.NET Core3.1 Ocelot認(rèn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
WPF集合控件實(shí)現(xiàn)分隔符(ItemsControl Separator)
這篇文章主要為大家詳細(xì)介紹了WPF集合控件實(shí)現(xiàn)分隔符ItemsControl Separator,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
asp.net下使用DbProviderFactories的數(shù)據(jù)庫(kù)操作類(lèi)
項(xiàng)目開(kāi)發(fā)中用到VB.NET開(kāi)發(fā),參考網(wǎng)上的資料,自己寫(xiě)了數(shù)據(jù)庫(kù)操作類(lèi)。2010-06-06
asp.net 利用NPOI導(dǎo)出Excel通用類(lèi)的方法
本篇文章主要介紹了asp.net 利用NPOI導(dǎo)出Excel通用類(lèi)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
aspnetcore 實(shí)現(xiàn)簡(jiǎn)單的偽靜態(tài)化功能
這篇文章主要介紹了aspnetcore 實(shí)現(xiàn)簡(jiǎn)單的偽靜態(tài)化功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07

