標籤:log 忽略 reload onchange 選項 完全 前言 filter cookie
1.前言
本文主要是以Visual Studio 2017 預設的 WebApi 模板作為基架,基於Asp .Net Core 1.0,本文面向的是初學者,如果你有 ASP.NET Core 相關實踐經驗,歡迎在評論區補充。
與早期版本的 ASP.NET 對比,最顯著的變化之一就是配置應用程式的方式, Global.asax、FilterConfig.cs 和 RouteConfig.cs 統統消失了,取而代之的是 Program.cs 和 Startup.cs。Program.cs 作為 Web 應用程式的預設入口,不做任何修改的情況下,會調用同目錄下 Startup.cs 中的 ConfigureServices 方法 和 Configure 方法。
應用啟動的流程
對於初學者來說,第一次面對 Startup.cs 往往無從下手,本文將一步步介紹作者的經驗,但是不會涉入到內部的代碼實現以及相關的原理,那並不是本文想要討論的範疇。
預設的Startup.cs
相信我,這將是你邁出構建靈活而強大的ASP.NET Core 應用程式的第一步。
2.配置參數選項
在官方文檔中提供多種方式來配置參數選項:
- 檔案格式(INI,JSON和XML)
- 命令列參數
- 環境變數
- 記憶體中的 .NET 對象
- 使用者機密儲存
- Azure 索引值
- 自訂提供者
雖然提供了很多選擇,但是我們只選擇其中的JSON檔案和環境變數來提供配置參數。
2.1 Json配置參數選項
參考官方文檔的樣本,我們在 appsettings.json 加入如下的參數:
appsettings.json
與此同時,我們還需要一個類來映射這些配置參數:
MyOptions.cs
思考一下 subsection 應該是字典還是一個對象?如果是字典,是否可以為<string,dynamic>或者<string,object>?
好了,現在就差怎麼讓他們聯絡起來,只需在 ConfigureServices 方法中將他們配對:
services.Configure<MyOptions>(Configuration);
最後就是解決怎麼使用這些配置參數的問題了,舉個最簡單的例子,我們可以在某個控制器中把我們的所有參數列印出來:
不知道你有沒有發現 MyOptions 類中有些屬性首字母大寫,有些屬性沒有,並不是與json檔案中完全一致,也就是說可以忽略大小寫。
回到之前的匹配環節,我們可以發現 services.Configure 的方法中似乎還有更多選擇,比如我們把之前的那一行代碼替換為:
services.Configure<MyOptions>(Configuration.GetSection("wizards"));//選擇wizards節點
我們可以發現返回的對象裡面的屬性都為null,這是因為json中的 "wizards"的節點並不能與我們的類匹配。
那麼問題來了,如果匹配的代碼如下,又會產生什麼樣的結果呢?先別急著回答,我會在下一節中給出答案。
2.2環境變數
環境變數,或者說系統變數,在windows中我們可以在系統屬性中配置:
在Linux環境中也有相應的配置,但是在開發過程中,我們可以在 Visual Studio 中配置:
在這之前,我們的Json檔案中已經有 "option1" 和 "option2"的參數選項,那麼會產生什麼樣的結果呢?
顯然我們可以看到環境變數的參數覆蓋了Json檔案的參數。而引起這種變化的原因還是需要回到Startup的初始化:
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)//必須的json檔案,並且自動重載 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)//不必須的json檔案 .AddEnvironmentVariables();//啟用環境變數 Configuration = builder.Build(); }
從中我們可以看出環境變數的配置在讀取 Json 檔案參數之後,這樣就會覆蓋已經存在的同名參數,而已經從 Json 檔案被匹配的參數並不會被清空(同樣適用於前一節提出的問題)。從另一方面來說,如果你不需要環境變數,則需要去掉 "AddEnvironmentVariables() ",以免覆蓋預期參數。
回到本節的中心,我們為什麼會需要環境變數呢?我個人會在Dockerfile中配置一些環境變數,比如某種服務的訪問地址、某中功能的開關等等。下面舉例說說兩個常用的環境變數:
ASPNETCORE_URLS 如果你沒有指定對應的 Url 監聽地址,可以通過該參數修改,如設定為 "http://*:80"。
ASPNETCORE_ENVIRONMENT 開發環境(Development)、預演環境(Staging)、生產環境(Production),將在工作環境一節中做詳細介紹。不同的工作環境將使得整個軟體流程變得清晰。
2.3配置參數小貼士
- 參數有多種來源,如不需要勿增來源。
- 要注意"最近原則",避免參數同名引起衝突。
- 參數的key可以忽略大小寫,所以環境變數中的 "OPTION2" 可以引起覆蓋 Json檔案中的 "option2" 的效果。
- 為複雜參數選擇合適的類型很重要,比如字典還是對象的取捨。
3.依賴注入
依賴注入在 ASP.NET Core 中無處不存在,在之前列印參數的例子中同樣用到。依賴注入好處都有啥?為什麼我們需要依賴注入?在 ASP .NET Core 中文文檔中依賴注入的章節 很好地解釋了:
你應該設計你的依賴注入服務來擷取它們的合作者。這意味著在你的服務中避免使用有狀態的靜態方法調用(代碼被稱為 static cling)和直接執行個體化依賴的類型。當選擇執行個體化一個類型還是通過依賴注入請求它時,它可以協助記住這句話, New is Glue。通過遵循 物件導向設計的 SOLID 原則,你的類將傾向於小、易於分解及易於測試。
3.1註冊服務以及簡單使用
為了方便下一節測試,我準備三個檔案,簡單的介面、該介面的實作類別,擁有介面成員的類:
IRepository
MemoryRepository
ProductTotalizer
接下來,我們使用 ASP.NET Core 內建的 DI 來註冊服務:
可以看到,註冊對象有很多種方法,並且我們可以管理對象的生命週期。註冊完對象,我們就可以在我們需要的地方注入對應的對象了,還是最簡單的例子——在控制器中使用:
對於控制器,我們有三種方式注入對象:建構函式、控制器動作、屬性注入。然而,在一般的類中,使用內建的 DI 只能是建構函式注入。到底是哪種方式好,見仁見智。
3.2生命週期
ASP.NET Core 服務可以被配置為以下生命週期:
- 瞬時(Transient) 在它們每次請求時都會被建立。這一生命週期適合輕量級的,無狀態的服務。
- 範圍 (Scoped) 在每次請求中只建立一次。
- 單例(Singleton) 在它們第一次被請求時建立(或者如果你在 ConfigureServices運行時指定一個執行個體)並且每個後續請求將使用相同的執行個體。
我們將通過逐步更改 IRepository 的生命週期來看看會發生什麼事情。
首先是瞬時:
接著是範圍:
最後是單例:
瞬時很好理解,類似每次都會new了一個對象。而對於範圍,如果一次請求中,有兩個甚至多個非單例對象引用到同一個範圍類型時,他們將會收穫同一個執行個體。單例也很好理解,從頭到尾都是同一個執行個體。
控制單一變數,如果只是改變 ProductTotalizer 的生命週期而不是改變 IRepository 的生命週期的話,會發生什麼情況呢?
3.3依賴注入小貼士
- 遵循 SOLID 原則,考慮一下哪些是需要依賴注入的。
- 合理考慮生命週期,假如某個類型在不同上下文中需要不同生命週期時,是否需要顯式命名區分?還是考慮結構是否合理?
- 避免靜態訪問服務。
- 避免靜態訪問 HttpContext 。
4.啟用擴充
在項目中我們往往會添加許多擴充,比如用於API文檔說明的Swagger、計劃任務的Hangfire、壓縮響應的GZIP、跨域訪問、日誌擴充等等。他們的共同點就是需要先安裝相應的nuget包,然後在 ConfigureServices() 方法中佈建服務,最後在 Configure() 方法中啟用。
我們以Swagger為例,首先是安裝對應的 nuget 包—— Swashbuckle。
接著是配置擴充:
最後就是啟用 Swagger 了:
我們訪問 Swagger 的地址看看效果:
5.中介軟體
中介軟體是用於組成應用程式管道來處理請求和響應的組件。管道內的每一個組件都可以選擇是否將請求交給下一個組件、並在管道中調用下一個組件之前和之後執行某些操作。請求委託被用來建立請求管道,請求委託處理每一個 HTTP 要求。
中介軟體處理請求
舉一個簡單的例子(更複雜的可以在中介軟體依賴注入對象),從 cookie 中擷取 token 並附加到要求標頭中:
與啟用擴充一樣,我們同樣是需要在 Configure()方法中啟用中介軟體:
app.UseMiddleware<JWTInHeaderMiddleware>();
如果我們有多個中介軟體呢,中介軟體的順序可能會影響到響應結果,但並不是總是線性相關的。例如,我們新增一個對響應狀態代碼處理的中介軟體:
我們把它加到其他中介軟體的最前面:
app.UseMiddleware<StatusCodeMiddleware>();//....app.UseMiddleware<JWTInHeaderMiddleware>();
雖然對狀態代碼處理的中介軟體是最前面,但可以在請求的最後關頭對請求結果進行處理。當然,如果中間有某個中介軟體短路了(沒有傳遞到下一個中介軟體),就會讓我們前功盡棄。
測試多個中介軟體處理請求6.過濾器
與中間相似,過濾器同樣可以對請求的前後執行特定代碼,但是過濾器可以配置為全域有效、僅對控制器有效或是僅對 Action 有效,比中介軟體更具有靈活性。
過濾器處理請求
另外,過濾器從類型上還能分為:授權過濾器、資源過濾器、Action過濾器、結果過濾器。很容易實現面向切面編程,降低了耦合。
這裡舉一個我最喜歡的過濾器——對請求的模型進行驗證:
在控制器或 Action 使用,只需加上特性即可:
當然,模型驗證的過濾器往往具有全域性,所以我一般是加在 services.AddMvc 中:
services.AddMvc(config=> { config.Filters.Add(new ValidateModel()); });
7.工作環境
ASP.NET Core 提供了許多功能和約定來允許開發人員更容易的控制在不同的環境中他們的應用程式的行為。當發布一個應用程式從開發到預演再到生產,為環境設定適當的環境變數允許對應用程式的調試,測試或生產使用進行適當的最佳化。
在軟體開發的生命週期中,在不同的工作環境中往往是不同的狀態。比如說,在測試或者預演環境中,啟用Swagger擴充、控制台列印日誌、允許跨域,而在生產環境中,往往處於安全、效能或者其他考慮,這些功能是需要禁止的。對於 MVC 開發人員來說,在開發過程中會使用本地的JS、Css、圖片等檔案,而在生產環境中這些檔案往往是從CDN中擷取。
8.結語
ASP.NET Core 提供了強大而靈活的配置機制,每個點展開都像是一片新的天地。即使是經驗豐富的開發人員,也不能自稱完全掌握全部機制。隨著 .NET Core 的完善和發展,Startup.cs 也將越來越複雜。越是複雜,就越是要小心,如無需要,勿增實體!
9.相關引用
- Configuration in ASP.NET Core
- ASP.NET Core 中文文檔
ASP.NET Core 菜鳥之路:從Startup.cs說起