日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core 沉思录 - 环境的思考

發布時間:2023/12/4 asp.net 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core 沉思录 - 环境的思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我的博客換新家啦,新的地址為:https://clrdaily.com :-D

今天我們來一起思考一下如何在不同的環境應用不同的配置。這里的配置不僅僅指 IConfiguration 還包含 IWebHostBuilder 的創建過程和 Startup 的初始化過程。

0 太長不讀

  • 環境造成的差異在架構中基本體現在 Infrastructure 中的各個 Adapter 中。而不應當入侵應用程序內部

  • 在 ASP.NET Core 中我們需要考慮如何將這些 Adapter(一)放在 service collection 中 (二)(可選)添加到 pipeline 中。

  • ASP.NET Core 默認提供了一系列手段來判斷當前的環境,只不過這些手段的設計奇怪且不完整。

  • IWebHostBuilder 的配制方法大多和環境相關,但 UseSetting 和環境無關。

  • 我們應當應用開閉原則,將相同環境的配置聚合起來,不同環境的配置進行統一抽象。方便維護和擴展。

  • 當我們進行設計的時候,需要注意不要將思路局限在 Framework 的設計上,而應當切實考慮我們真正希望解決的問題。

1 架構層面的思考

Web Service 的開發和部署過程會涉及若干環境。總的來說可以分為開發環境和部署環境。而部署環境往往又分為 QA、Stage 和 Production 等。對于不同的環境,應用程序可能需要應用不同的配置或實現。還是回到架構的層面上,如下圖:


那么這種不同應該體現在架構的哪一個層面上呢?應當讓這些不同體現在 Infrastructure 的那些 Adapters 上。因為 Adapter 是其中直接和環境相關的部分。

用一個典型的例子來表示。假定一個注冊用戶 Account 的業務。在 Application Service 層面,我們提供了如下的接口:

public?class?AccountRegistrationService?{
????public?AccountRegistrationResult?Register(AccountRegistrationRequest?request)?{
????????Account?account?=?this.repository.CreateDetached();
????????//?initialize?account?from?request
????????account.Save();
????????return?AccountRegistrationResult.Create(account);
????}
}

在 Domain 層面我們有代表領域對象 Account 的類型 Account。Account 類型的 Save() 方法可以保存賬戶信息,其中的實現類似:

public?class?Account?{
????...

????public?void?Save()?{
????????this.repository.Save(this);
????}
}

而其中的 repository 則依賴 UnitOfWork 而 UnitOfWork 則可能依賴于具體的持久化實現或者依賴于其他遠程服務:

public?class?AccountRepository?{
????readonly?IUnitOfWork?session;

????public?Account?CreateDetached()?{
????????return?new?Account(this);
????}

????public?void?Save(Account?account)?{
????????this.session.RegisterNew(account);
????}
}

在這個例子中,AccountService 屬于 Application Service 層面,Account 和 AccountRepository 則屬于 Domain 層面。這兩層的依賴關系是 Application Service 依賴于 Domain。而 Domain 中的 UnitOfWork 則是一個接口。假設我們需要將數據寫入數據庫。則這個接口的實現需要持久化的支持例如它需要使用特定的 IDbConnection (Adapter)。即 IUnitOfWork 的實現位于 Infrastructure 層,并在 Infrastructure 層調用 Adapter 向 DB 中寫入信息。

而對于不同的環境則可以使用不同的實現,例如,對于運行單元測試的環境,我們不妨叫她 Test 環境。這個 DB 很有可能是一個 in memory 的 SQLite 數據庫。而在生產環境則是 MySQL 的集群。

應用程序的內部邏輯最終全部依賴與特定的抽象或接口。它們全部嚴密的包裹在 Infrastructure 之中,并和外部環境完全隔離。而 Infrastructure 中的 Adapter 則負責聯系外部環境。綜上所述,環境相關的變化應當全部封閉在 Infrastructure 中。

2 ASP.NET Core 中的對應關系

ASP.NET Core 應用程序中的組件的初始化由兩個部分構成,第一個部分就是將組件中的類型添加到依賴注入的 IServiceCollection 實例中,以便進行創建;第二個部分(可選)即將組件通過 IApplicationBuilder 添加到應用程序的處理流水線中。我們一個一個來思考。

2.1 依賴注入

ASP.NET Core Web Application 中用依賴注入來決定某種抽象的實現類型。但需要指出的是 ASP.NET 應用程序的依賴注入是分兩個階段進行的。(我們將在另外一篇中介紹),簡單來說 ServiceCollection 的構建分為兩個部分:

  • 為了構建宿主環境而添加的類型;(Infrastructure 層)

  • 為了應用程序本身而添加的 Framework(例如 MvC)和各種業務類型。(Infrastructure 層,Application + Domain 層)。

而和環境相關的部分主要位于 “為了構建宿主環境而添加的類型” 中。這一部分的代碼屬于在 IStartup 初始化之前的 WebHostBuilder 構建代碼中。一般來說,我們習慣于將 UseStartup 調用放在 IWebHostBuilder 實例創建的最后,那么也就是 UseStartup 之前的代碼:

public?static?IWebHostBuilder?CreateWebHostBuilder(string[]?args)
{
????return?new?WebHostBuilder()
????????.UseKestrel()
????????.ConfigureLogging(...)
????????//
????????//?The?configurations?before?UseStartup?are?environment?specific
????????//
????????.UseStartup<Startup>();
}

2.2 流水線

在流水線配置中主要考慮的是 Web 輸入輸出上的的變化。例如 Production 環境需要配置 SSL,消除敏感 Header,消除詳細的 Error Information 等等。

將組件配置到應用程序的流水線的操作是在 IStartup 接口的實現中進行的。定義 IStartup 接口實現的方式大體有兩種,第一種是調用 WebHostBuilderExtensions.Configure 方法,另一種是使用 WebHostBuilderExtensions.UseStartup 方法。不論使用何種方式最終都會歸結到對 IApplicationBuilder 的操作:

public?void?Configure(IApplicationBuilder?app)?{
????//?building?pipeline
}

在這個時候,宿主初始化相關的類型已經全部可以使用了。因此取用環境相關的信息(環境類型,配置等)就更方便了。

3 落地

ASP.NET Core 對這個環節的設計很奇怪。一方面,它提供了非常底層的基于 IHostingEnvironment.EnvironmentName 的值來進行環境區分的方法。例如,官方范例中往往會使用如下的代碼:

new?WebHostBuilder()
????.UseKestrel()
????.ConfigureLogging((context,?logBuilder)?=>?{
????????if?(context.HostingEnvironment.IsDevelopment())?{
????????????...
????????}
????????else?if?(context.HostingEnvironment.IsProduction())?{
????????????...
????????}
????????else?{
????????????...
????????}
????})
????...

而另一方面卻又在 Startup 上設計了命名的 Convension。例如:

class?DevelopmentStartup?{}?????//?for?Development
class?ProductionStartup?{}??????//?for?Production
class?Startup?{}????????????????//?fallback

...

webHostBuilder.UseStartup(assemblyName);

又例如:

class?Startup??{
????public?void?ConfigureServices(IServiceCollection?services)?{?}
????public?void?ConfigureStagingServices(IServiceCollection?services)?{?}
????public?void?Configure(IApplicationBuilder?app,?IHostingEnvironment?env)?{?}
????public?void?ConfigureStaging(IApplicationBuilder?app,?IHostingEnvironment?env)?{?}
}

這些設計差異很大且每一個都不徹底。而在實際項目中環境屬于一個擴展點;而每一套環境的各項配置應當是內聚的。因此上述幾種方式或多或少會增加維護上的成本。而較好的設計應當針對如下三個問題:

  • 能夠立刻說出,我的系統支持幾種環境;

  • 每一種環境的各種類型的配置(例如,配置源、日志記錄、HTTP Client、數據庫)是什么樣子的,有什么差異;

  • 能不能用兩步添加一個新的環境:第一,一次性創建一個新環境的所有配置,第二,將這個環境納入到系統初始化過程中。

為了達到這個要求,需要考慮統一的實現手段。

3.1 在 WebHost 開始構建之前我們并不能確定環境信息

一個最簡單的想法就是根據不同的環境采取兩種完全不同的 WebHostBuilder 配置流程。例如:

WebHostBuilder?builder?=?new?WebHostBuilderFactory().Create(env.EnvironmentName);

遺憾的是這種設計本身是有問題的。首先,若干環節都可以影響環境的最終確定,包括:

  • 當前 Session 的 ASPNETCORE_ENVIRONMENT 的值;(請參見 https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs#L44)

  • Properties/launchSettings.json 中選定 Profile 中 ASPNETCORE_ENVIRONMENT 的值(如果用 dotnet run 命令執行的話)

  • WebHostBuilder.UseEnvironment(name) 的參數值;

  • WebHostBuilder.UseSetting(key, value) 當 key 為 WebHostDefaults.EnvironmentKey 時的值。

  • 若 Host 在 IIS 中,則 web.config 中關于 environmentVariable 的設置。

因此只有在 WebHostBuilder 開始 Build 時,我們才可以最終確定環境名稱。

3.2 `UseSetting` 并不是環境相關的

另一種方案是包裝 IWebHostBuilder 使其能夠依據環境做出相應的 Dispatch。例如:

abstract?class?EnvironmentAwareWebHostBuilder?:?IWebHostBuilder?{
????IWebHostBuilder?UnderlyingBuilder?{?get;?}
????protected?abstract?bool?IsSupported(IHostingEnvironment?hostingEnvironment);

????protected?EnvironmentAwareWebHostBuilder(IWebHostBuilder?underlyingBuilder)
????
{
????????//?Validation?omitted
????????UnderlyingBuilder?=?underlyingBuilder;
????}

????//?...
}

從而我們可以分別為不同的環境進行相應的配置。以 ConfigureService 方法為例:

public?IWebHostBuilder?ConfigureServices(Action<IServiceCollection>?configureServices)
{
????UnderlyingBuilder.ConfigureServices(
????????(context,?services)?=>
????????{
????????????if?(!IsSupported(context.HostingEnvironment))?{?return;?}

????????????configureServices(services);
????????});
????return?this;
}

按照上述方式包裝 ConfigureAppConfiguration,這樣就可以構造以下的擴展方法:

public?static?IWebHostBuilder?UseEnvironment(
????this?IWebHostBuilder?builder,
????string?environmentName,?
????Action<IWebHostBuilder>?configureBuilder
)
{
????bool?IsEnvironmentSupported(IHostingEnvironment?h)?=>?
????????h.IsEnvironment(config.environmentName);

????EnvironmentAwareWebHostBuilder?environmentAwareBuilder?=
????????new?DelegatedWebHostBuilder(builder,?IsEnvironmentSupported);
????config.configureBuilder(environmentAwareBuilder);

????return?builder;
}

這種方案下的 WebHostBuilder 初始化邏輯就變成了:

webHostBuilder
????.UseEnvironment("Development",?wb?=>?{
????????wb
????????????.ConfigureService((ctx,?cb)?=>?{?...?})
????????????.ConfigureLogging((lb)?=>?{?...?})
????????????...
????})
????.UseEnvironment("Production",?wb?=>?{
????????//?configure?for?production
????});

這樣我們至少就可以用若干擴展方法類將不同環境完全分開了。但是這個實現方案是有問題的:UseSetting 方法。IWebHostBuilder 所公開的方法中除了 Build、ConfigureServices 和 ConfigureAppConfiguration 之外還有第四個方法:UseSetting。和上述 ConfigureXxx 方法不同,UseSetting 方法執行完畢之后其影響馬上生效,而且該方法無法根據不同的環境作出變化。即,如果我們使用了:

webHostBuilder
????.UseEnvironment("Development",?wb?=>?wb.UseSetting("Foo",?"Bar"))
????.UseEnvironment("Production",?wb?=>?wb.UseSetting("Foo",?"O_o"));

且當前環境為 Development 則 IConfiguration 實例的 "Foo" 對應的值為 "O_o"。這就會造成混淆。

3.3 還是從擴展點來思考

從第 2 節的論述中我們已經知道和環境相關的配置可能存在于宿主環境初始化過程中,也可能存在 Startup 初始化過程中(即 WebHost.Run 方法執行過程中)。因此我們必須綜合考慮這兩個部分,但是這個兩個部分天生是不同的。那么強行進行統一也是不合適的。

根據開閉原則,我們還是應該從擴展點上來考慮。首先我們能夠確定我們的 Adapter 有哪些。又有哪一些 Adapter 是和環境相關的。例如我們和環境相關的 Adapter 有 DB,配置文件加載,日志記錄,HttpClient(在非 Development 環境中我們可能需要進行客戶端證書驗證),在流水線創建過程中需要根據環境配置是否需要 HTTPS 強制跳轉,需要配置錯誤信息的詳細程度等等。在梳理好這些內容后我們就能有針對性的創建方法對各個部分進行配置了,我們可以使用工廠模式:

class?WebHostConfigureFactory?{
????...

????public?IWebHostConfigurator?Create(string?environmentName)?{
????????return?cachedConfigurators[environmentName];
????}
}

而每一個 IWebHostConfigurator 中都包含了所有的環境相關配置:

interface?IWebHostConfigurator?{
????void?AddDatabase(IHostingEnvironment?environment,?IServiceCollection?services);
????void?LoadConfiguration(IHostingEnvironment?environment,?IConfigurationBuilder?configBuilder);
????void?ConfigureLogging(IHostingEnvironment?environment,?ILoggingBuilder?loggingBuilder);
????void?AddHttpClient(IHostingEnvironment?environment,?IServiceCollection?services);
????void?ConfigureHttpsRedirection(IHostingEnvironment?environment,?IConfiguration?configuration,?IApplicationBuilder?builder);
????void?ConfigureErrorHandler(IHostingEnvironment?environment,??IConfiguration?configuration,?IApplicationBuilder?builder);
}

而這樣我們為各個環境的擴展點建立了抽象,從而統一配置過程:

static?IWebHostBuilder?CreateWebHostBuilder()?{
????return?new?WebHostBuilder()
????????.UseKestrel()
????????//
????????//?Common?configurations
????????//
????????.ConfigureServices((context,?services)?=>?{
????????????IWebHostConfigurator?configurator?=?factory.Create(context.HostingEnvironment.EnvironmentName);

????????????configurator.AddDatabase(context.HostingEnvironment,?services);
????????????configurator.AddHttpClient(context.HostingEnvironment,?services);
????????})
????????.ConfigureLogging((context,?logBuilder)?=>?{
????????????factory
????????????????.Create(context.HostingEnvironment.EnvironmentName)
????????????????.ConfigureLogging(context.HostingEnvironment,?logBuilder);
????????})
????????.UseStartup<Startup>();
}

...

class?Startup?{
????...

????public?void?Configure(IApplicationBuilder?app)?{
????????IWebHostConfigurator?configurator?=?factory.Create(hostingEnvironment.EnvironmentName);
????????configurator.ConfigureHttpsRedirection(hostingEnvironment,?configuration,?app);
????????configurator.ConfigureErrorHandler(hostingEnvironment,?configuration,?app);

????????//?Common?configurations
????}
}

4 總結

請跳到文章開頭 :-D

參考資料

  • R. C. Martin and R. C. Martin, Clean architecture: a craftsman’s guide to software structure and design. London, England: Prentice Hall, 2018.

  • Unit-of-work: https://martinfowler.com/eaaCatalog/unitOfWork.html

  • Dependency Injection in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2

  • App startup in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.2

  • Use multiple environments in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.2

如果您覺得本文對您有幫助,也歡迎分享給其他的人。我們一起進步。歡迎關注我的微信公眾號:

總結

以上是生活随笔為你收集整理的ASP.NET Core 沉思录 - 环境的思考的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美日韩不卡视频 | 欧美在线观看a | 免费国产黄色 | 日本少妇色视频 | 国产人妻黑人一区二区三区 | 免费吸乳羞羞网站视频 | 日本激情一区二区三区 | 日韩三级一区二区 | 国产一区二区黑人欧美xxxx | 99精品视频在线观看免费 | www视频免费在线观看 | 色天使亚洲 | 西西人体44www大胆无码 | 尤物视频免费在线观看 | 男人久久天堂 | 超碰人人国产 | 国产黄色小视频在线观看 | 国产大奶在线观看 | 最新啪啪网站 | 成人免费视频观看 | 日韩在线播放一区二区 | 香蕉国产999 | 日日射天天操 | 天天爱天天爽 | 能看av的网址 | 依依成人在线视频 | 国产农村妇女精品一区二区 | 在线观看你懂的网址 | 午夜免费网址 | 爱情岛论坛自拍 | 日产毛片 | 波多野结衣亚洲一区二区 | 国产精品夜夜躁视频 | 欧美日韩中文国产一区发布 | 中文字幕一区视频 | 日本bbwbbw | 蜜桃无码一区二区三区 | 狠狠撸狠狠干 | 69久久久| 成人性生交大片免费看r链接 | 久草五月| 99久久久无码国产精品性青椒 | 狠狠躁天天躁夜夜躁婷婷 | 久久久亚洲成人 | 特黄做受又粗又大又硬老头 | av影院在线观看 | 好了av在线 | 欧美中文字幕在线播放 | 天堂网www.| 精品久草 | 美女大bxxxxn内射 | 在线天堂6 | 69av视频在线 | 蜜桃做爰免费网站 | 免费的黄色网 | 美美女高清毛片视频免费观看 | 成人av番号网 | 欧美小视频在线 | 国产高清一区二区三区 | 福利毛片 | 性色av一区二区三区 | 免费成人在线电影 | 亚洲一区在线免费 | 91蝌蚪视频在线 | 人人叉人人 | 在线视频a| 在线午夜电影 | 亚洲午夜精品视频 | 精品国产AV色欲天媒传媒 | 在线观看视频一区二区 | av在线视屏 | 国内av在线 | 波多野结衣中文字幕一区 | 在线香蕉 | 日本护士取精xxxxxhd | 激情小说中文字幕 | 午夜av在线免费观看 | 人妻丰满熟妇av无码久久洗澡 | 免费黄网站在线观看 | 久久国产精品电影 | 国产自偷自拍 | 成年人黄色片网站 | 国产一级免费看 | 国产午夜精品一区二区理论影院 | 欧美精品手机在线 | 成年人在线视频网站 | 日本性久久 | 蜜桃无码一区二区三区 | www.久热 | 日韩欧美黄色片 | 在线日韩亚洲 | 波多野结衣精品在线 | 自拍第1页 | 四虎网站最新网址 | 欧美1区2区3区4区 | 另类男人与善交video | 中文一二区 | 加勒比日韩| 亚洲最大福利视频 |