為ASP.NET Core強(qiáng)類型配置對象添加驗(yàn)證的方法
前言
本篇博客中,我將描述如何在ASP.NET Core程序啟動(dòng)時(shí),確保強(qiáng)類型配置對象正確的綁定成功。通過使用IStartupFilter接口對象,你可以更早的驗(yàn)證你的配置對象是否綁定了正確的值,并不需要等待程序啟動(dòng)之后的某個(gè)時(shí)間點(diǎn)再驗(yàn)證。
這里我將簡單描述一下ASP.NET Core的配置系統(tǒng),以及如何使用強(qiáng)類型配置。我將主要描述一下如何去除對IOptions接口的依賴,然后我會(huì)描述一下強(qiáng)類型配置對象綁定不正確的問題。最后,我將給出一個(gè)在程序啟動(dòng)時(shí)驗(yàn)證強(qiáng)類型配置對象的方案。
ASP.NET Core中的強(qiáng)類型配置
ASP.NET Core的配置系統(tǒng)非常的靈活,它允許你從多種數(shù)據(jù)源中讀取配置信息,例如Json文件,YAML文件,環(huán)境變量,Azure Key Vault等。官方推薦方案是使用強(qiáng)類型配置來獲取IConfiguration接口對象的值。
強(qiáng)類型配置使用POCO對象來呈現(xiàn)你的程序配置的一個(gè)子集,這與IConfiguration接口對象存儲(chǔ)的原始鍵值對不同。例如,現(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類中使用擴(kuò)展方法Configure,將強(qiáng)類型配置對象和你程序配置綁定起來。
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)前方法所在類的構(gòu)造函數(shù)中注入一個(gè)IOptions接口對象,即可使用這個(gè)對象的Value屬性,獲取到配置的值, 這里ASP.NET Core配置系統(tǒng)自動(dòng)幫你完成了強(qiáng)類型對象和配置之間的綁定。
public class TestController : Controller { private readonly SlackApiSettings _slackApiSettings; public TestController(IOptions<SlackApiSettings> options) { _slackApiSettings = options.Value } public object Get() { return _slackApiSettings; } }
解除對IOptions接口的依賴
可能有些人和我一樣,不太喜歡讓自己創(chuàng)建的類依賴于IOptions接口,我們只希望自己創(chuàng)建的類僅依賴于配置對象。這里你可以使用如下所述的方法來解除對IOptions接口的依賴。這里我們可以在依賴注入容器中顯式的注冊一個(gè)SlackApiSetting配置對象,并將解析它的方法委托給一個(gè)IOptions對象
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è)“原始”的配置對象了。
public class TestController : Controller { private readonly SlackApiSettings _slackApiSettings; public TestController(SlackApiSettings settings) { _slackApiSettings = settings; } public object Get() { return _slackApiSettings; } }
這個(gè)解決方案通常都非常有效, 但是如果配置出現(xiàn)問題,例如在JSON文件中出現(xiàn)了錯(cuò)誤拼寫,這里會(huì)發(fā)生什么事情呢?
如果綁定失敗,程序會(huì)發(fā)生什么事情?
我們綁定強(qiáng)類型配置對象的時(shí)候有以下幾種錯(cuò)誤的可能。
節(jié)點(diǎn)名稱拼寫錯(cuò)誤
當(dāng)你綁定配置的時(shí)候,你需要顯式的指定綁定的配置節(jié)點(diǎn)名稱,如果你當(dāng)前使用的appsetting.json作為配置文件,json文件中的key即是配置的節(jié)點(diǎn)名稱。例如下面代碼中的"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)類型配置對象SlackApiSetting, 你需要調(diào)用一下代碼
services.Configure<SlackApiSettings>(Configuration.GetSection("SlackApi"));
這時(shí)候,假設(shè)我們將appsettings.json中的"SlackApi"錯(cuò)誤的拼寫為"SackApi"。現(xiàn)在我們?nèi)フ{(diào)用前面例子中的TestController中的GET方法,會(huì)得到一下結(jié)果
{ "webhookUrl":null, "displayName":null, "shouldNotify":false }
所有的key都是綁定了他們的默認(rèn)值,但是沒有發(fā)生任何錯(cuò)誤,這意味著他們綁定到了一個(gè)空的配置節(jié)點(diǎn)上。這看起來非常糟糕,因?yàn)槟愕拇a并沒有驗(yàn)證webhookUrl是否是一個(gè)合法的Url。
屬性名拼寫錯(cuò)誤
相似的,有時(shí)候拼寫的節(jié)點(diǎn)名稱正確,但是屬性名稱可能拼寫錯(cuò)誤。例如, 我們將appSettings.json文件中的"WebhookUrl"錯(cuò)誤的拼寫為"Url"。這時(shí)我們調(diào)用前面例子中的TestController中的GET方法,會(huì)得到以下結(jié)果
{ "webhookUrl":null, "displayName":"My fancy bot", "shouldNotify":true }
強(qiáng)類型配置類的屬性缺少SET訪問器
我經(jīng)常發(fā)現(xiàn)一些初級程序員會(huì)遇到這個(gè)問題,針對屬性,他們只提供了GET訪問器,而缺少SET訪問器,在這種情況下強(qiáng)類型配置對象是不會(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 }
不兼容的類型值
最后一種情況就是將一個(gè)不兼容的類型值,綁定到屬性上。在配置文件中,所有的配置都是以文本形式保存的,但是綁定器需要將他們轉(zhuǎn)換成.NET中支持的基礎(chǔ)類型。例如ShouldNotify屬性是一個(gè)布爾類型的值,我們只能將"True", "False"字符串綁定到這個(gè)值上,但是如果你在配置文件中,設(shè)置該屬性的值為"THE VALUE", 當(dāng)程序訪問TestController時(shí),程序就會(huì)報(bào)錯(cuò)
使用IStartupFilter創(chuàng)建一個(gè)配置驗(yàn)證
為了解決這個(gè)問題,我將使用IStartupFilter創(chuàng)建一個(gè)在應(yīng)用啟動(dòng)時(shí)運(yùn)行的簡單驗(yàn)證步驟,以確保你的設(shè)置正確無誤。
IStartupFilter接口允許你通過向依賴注入容器添加服務(wù)來間接控制中間件管道。 ASP.NET Core框架使用它來執(zhí)行諸如“將IIS中間件添加到應(yīng)用程序的中間件管道的開頭, 或添加診斷中間件之類”的操作。
雖然IStartupFilter經(jīng)常用來向管道中添加中間件,但是我們也可以不這么做。相反的,我們可以在程序啟動(dòng)時(shí)(服務(wù)配置完成之后,處理請求之前),使用它來執(zhí)行一些簡單的代碼。
這里首先我們創(chuàng)建一個(gè)簡單的接口,強(qiáng)類型配置類可以通過實(shí)現(xiàn)這個(gè)接口來完成一些必要的驗(yàn)證。
public interface IValidatable { void Validate(); }
下一步,我們創(chuàng)建一個(gè)SettingValidationStartupFilter類, 它實(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ù)中,我們從依賴注入容器中取出了所有實(shí)現(xiàn)IValidatable接口的強(qiáng)類型配置對象,并在Configure方法中依次調(diào)用他們的Validate方法。
SettingValidationStartupFilter并沒有修改任何中間件管道, Configure方法中直接返回了next對象。但是如果某個(gè)強(qiáng)類型配置類的驗(yàn)證失敗,在程序啟動(dòng)時(shí),就會(huì)拋出異常,從而阻止了程序。
接下來我們需要在Startup類中注冊我們創(chuàng)建的服務(wù)SettingValidationStartupFilter
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IStartupFilter, SettingValidationStartupFilter>() // 其他配置 }
最后你需要讓你的配置類實(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來實(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); } }
無論你使用哪一種方式,如果綁定出現(xiàn)問題,程序啟動(dòng)時(shí)都會(huì)拋出異常。
最后一步,我們需要將SlackApiSettings 以IValidatable接口的形式注冊到依賴注入容器中,這里我們同樣可以使用前文的方法解除對IOptions接口的依賴。
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); }
測試結(jié)果
我們可以任選之前列舉的一個(gè)錯(cuò)誤方式來進(jìn)行測試,例如,我們將WebhookUrl錯(cuò)誤的拼寫為Url。 當(dāng)程序啟動(dòng)時(shí),就會(huì)拋出以下異常。
原文: Adding validation to strongly typed configuration objects in ASP.NET Core
作者: Andrew Lock
譯文: Lamond Lu
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Asp.Net防止刷新重復(fù)提交數(shù)據(jù)的辦法
最近在用Asp.Net編寫點(diǎn)東西時(shí)遇到個(gè)問題:即用戶在提交表單后按刷新就會(huì)重復(fù)提交數(shù)據(jù),即所謂的“刷新重復(fù)提交”的問題。2013-03-03.net indexOf(String.indexOf 方法)
字符串的IndexOf()方法搜索在該字符串上是否出現(xiàn)了作為參數(shù)傳遞的字符串,如果找到字符串,則返回字符的起始位置 (0表示第一個(gè)字符,1表示第二個(gè)字符依此類推)如果說沒有找到則返回 -12012-10-10ASP.NET下上傳圖片到數(shù)據(jù)庫,并且讀出圖片的代碼(詳細(xì)版)
上傳圖片到數(shù)據(jù)庫,從數(shù)據(jù)庫的創(chuàng)建到數(shù)據(jù)庫中圖片的現(xiàn)實(shí)都給出了具體的代碼,因?yàn)閍sp.net版本的問題,大家可能需要稍微修改下。2010-07-07ASP.NET MVC下基于異常處理的完整解決方案總結(jié)
ASP.NET MVC是一個(gè)極具可擴(kuò)展開發(fā)框架,在這篇文章中我將通過它的擴(kuò)展實(shí)現(xiàn)與EntLib的集成,并提供一個(gè)完整的解決異常處理解決方案。2017-01-01Asp.net利用JQuery彈出層加載數(shù)據(jù)代碼
最近看QQ空間里面的投票功能很使用。點(diǎn)擊一個(gè)鏈接就彈出一個(gè)層,然后再加載一些投票信息,旁邊的區(qū)域變成灰色不可用狀態(tài)。其實(shí)這不算什么高深的技術(shù),只要在ASP.NET中利用JQuery結(jié)合一般處理程序ASHX即可搞定了。2009-11-11使用HtmlAgilityPack XPath 表達(dá)式抓取博客園數(shù)據(jù)的實(shí)現(xiàn)代碼
使用HtmlAgilityPack XPath表達(dá)式來抓取博客園數(shù)據(jù)使用WebClient 下載數(shù)據(jù),HtmlAgilityPack XPath表達(dá)式解析數(shù)據(jù),并綁定到Repeater控件2011-12-12.NET Core應(yīng)用類型(Portable apps & Self-contained apps)
這篇文章主要介紹了.NET Core應(yīng)用類型,通常類型用來描述一個(gè)特定的執(zhí)行模型或者基于此的應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04CommunityServer又稱CS論壇的相關(guān)學(xué)習(xí)資料
以前項(xiàng)目需要整合這個(gè)論壇,同事找了一些資料,現(xiàn)在放上來,并說下自己對這個(gè)論壇的看法。2009-05-05詳解.Net core2.0日志組件Log4net、Nlog簡單性能測試
這篇文章主要介紹了詳解.Net core2.0日志組件Log4net、Nlog簡單性能測試,比較log4net、nlog的文件寫入性能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07