ASP.NET與ASP.NET Core很類似,但它們之間存在一些細微區別以及ASP.NET Core中新增特性的使用方法,在此之前也寫過一篇簡單的對比文章ASP.NET MVC應用遷移到ASP.NET Core及其異同簡介,但沒有進行深入的分析和介紹,在真正使用ASP.NET Core進行開發時, ...
ASP.NET與ASP.NET Core很類似,但它們之間存在一些細微區別以及ASP.NET Core中新增特性的使用方法,在此之前也寫過一篇簡單的對比文章ASP.NET MVC應用遷移到ASP.NET Core及其異同簡介,但沒有進行深入的分析和介紹,在真正使用ASP.NET Core進行開發時,如果忽略這些細節可能會出現奇怪的問題,特此將這些細節進行分享。
本文主要內容有:
- 無處不在的依賴註入(Dependency Injection, DI)
- Configuration&Options
- ASP.NET Core 請求管道建立
- ASP.NET Core Mvc
- Web API
- Signalr
- 小結
註:本文基於ASP.Net Core 2.1版本,.Net Core SDK版本需要2.1.401+。長篇預警( ╯□╰ )
無處不在的依賴註入
ASP.NET與ASP.NET Core之間最大區別之一就是內置了依賴註入機制,雖然ASP.NET中也有DI機制,但沒有內置容器,一般都需要使用第三方的容器來提供服務,另外依賴註入的概念也不像ASP.NET Core中這樣無處不在。
簡單來說依賴註入的目的是為了讓代碼解耦以提高代碼的可維護性,同時也要求代碼設計符合依賴導致原則使得代碼更加靈活,而其原理實際上就是在應用程式中添加一個對象容器,在應用初始化時將實際的服務“放”到容器中,然後當需要相應服務時從容器中獲取,由容器來組裝服務。
服務的註冊
ASP.NET Core的Startup(註:Startup僅僅只是約定名稱,實際使用是在Program類型中創建 WebHost時使用的),該類型中包含兩個方法分別是ConfigureServices和Configure,其中ConfigureServices的主要作用就是用來將服務“放”置到容器中
代碼來自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1
替換預設的依賴註入容器
ASP.NET Core的預設容器僅提供了構造註入功能,如果需要使用屬性註入等功能或者在遷移時原有應用依賴於其它容器,那麼可以通過使用第三方容器實現。
將預設容器替換為其它容器僅需三步:
1. 將ConfigureServices方法的返回類型改為IServiceProvider。
2. 將ASP.NET Core中的服務註冊到第三方容器中。
3. 使用第三方容器實現IServiceProvider介面並返回。
官方文檔以Autofac為例,Autofac已經實現了ASP.NET Core服務註冊到Autofac容器中,以及Autofac容器的IServiceProvider介面封裝,僅需安裝Autofac以及Autofac.Extensions.DependencyInjection包即可。
詳情參考:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#default-service-container-replacement
使用windsor或其它容器可以參考:
https://stackoverflow.com/questions/47672614/how-to-use-windsor-ioc-in-asp-net-core-2
將Controller註冊為服務
雖然Controller在激活時是通過容器來獲取Controller的依賴(即構造方法需要的參數),在代碼運行的時候給人一種Controller是從容器中組裝的錯覺,但是實際上預設情況下Controller的組裝過程不是直接由容器組裝的,如果要讓Controller從容器組裝,那麼在配置MVC服務時需要通過.AddControllerAsServices()方法將Controller註冊到容器中:
註:一般情況下是否將Controller註冊為服務對Controller的開發和代碼的運行並沒有很大區別,但是如果當容器變更為其它容器,並且使用了容器提供的如屬性註入等功能時,如果沒有將Controller註冊為服務,那麼相應的屬性註入的過程也不會被觸發,簡單來說就是只有將Controller註冊為服務,那麼實例化Controller的工作才會由容器完成,才會觸發或者使用到容器提供的其它特性。
服務的獲取
前面介紹了服務的註冊,現在來介紹一下在ASP.NET Core中有哪些方法可以獲取服務:
1. Controller構造方法參數。
2. 通過Controller註入IServiceProvider類型,通過IServiceProvider來獲取服務:
3. 在Action方法或者Mvc過濾器(過濾器的上下文參數中包含HttpContext)中通過HttpContext的RequestServices對象獲取服務:
4. 在View上通過@inject註入服務:
5. 在Action方法中,通過FormServices特性註入:
註:一般來說儘可能顯式的標明類型的依賴(即通過構造參數的方式聲明當前類型所依賴的組件),上面的2和3兩點分別都是通過服務提供器在方法內部來獲取依賴,這樣做依賴對於外界來說是不可知的,可能會對代碼的可維護、可測試性等造成一定影響,這種模式被稱為Service Locator模式,在開發過程中儘可能避免Service Locator模式的使用。
常用的服務
ASP.NET Core相對於ASP.NET來說取消了一些常用的靜態類型,比如HttpContext、ConfigurationManager等,取而代之的是通過將類似的組件以服務的形式註冊到容器中,使用時通過容易來獲取相應的服務組件,這些常用的服務有:
1. IHostingEnvironment:包含了環境名稱、應用名稱以及當前應用程式所處的根目錄及Web靜態內容的根目錄(預設wwwroot)。
2. IHttpContextAccessor:從名字可以看出,它用來訪問當前請求的HttpContext。
3. IConfiguration:ASP.NET Core配置信息對象。
4. IServiceProvider: ASP.NET Core服務提供器。
5. DbContext: 這裡的DbContext指的是EFCore的DbContext,在ASP.NET Core中,EFCore的DbContext也是在ConfigureServices方法中進行配置並添加到容器,使用時直接從容器中獲取(但要註意的是對於分層結構的開發風格來說,DbContext不會直接被Controller依賴,而是被Controller中依賴的業務服務類型所以來,就是說編寫Controller代碼的時候不會直接與DbContext發生直接交互)。
Configuration&Options
在ASP.NET的開發中,通常某個變數需要從配置文件讀取,一般都是在相應類型的構造方法中,通過靜態類型ConfigurationManager的AppSettings方法來讀取並初始化變數。雖然ASP.NET Core也可以在類型中註入IConfiguration實例來直接讀取配置文件,但該方法由於Options模式的出現已經不再建議使用,使用組件通過依賴相應的組件Options可以做到關註點分離,提高程式的靈活性、可拓展性,Options使用方法見文檔:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1
ASP.NET Core 請求管道建立
ASP.NET由於是基於IIS請求管道的,ASP.NET應用程式僅僅是管道中的一個處理環節,管道中還包含如身份驗證、靜態文件處理等環節,但ASP.NET Core不一樣,它脫離了IIS處理管道,所以整個管道的建立均需要靠程式自身完成,而ASP.NET Core建立管道的代碼就是Startup類型的Configure方法,該方法通過IApplicationBuilder實例來添加不同功能的中間件,通過中間件的串聯形成處理管道,下圖是ASP.NET Mvc模板生成的管道代碼:
圖片來自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1#the-configure-method
該管道主要包含了錯誤處理(開發環境顯示異常信息,其它環境跳轉錯誤頁面)中間件、靜態文件處理中間件以及Mvc中間件。
更多中間件可參考文檔:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index?view=aspnetcore-2.1
ASP.NET Core Mvc
ASP.NET Core Mvc與ASP.NET Mvc相比整體上區別不大,但仍然有很多細節上的變化,下麵就開始一一介紹:
路由
路由的作用是將請求根據Url映射到“對應”的處理器上,在Mvc中請求的終點就是Controller的Action方法,而這裡所謂的“對應”指的是Url與路由模板的匹配,ASP.NET Core Mvc通過以下的方式添加路由模板:
上圖中的路由模板是最常用的路由模板,使用花括弧內的內容為路由參數及其預設值,Url中通過路由參數控制器名稱、活動方法名稱來匹配到相應控制器的活動方法。
在註冊路由時可以為相應路由添加預設值、路由參數約束以及對應路由的相關附加數據(datatokens):
路由的功能除了處理請求匹配外,還具有鏈接生成的功能,特別是Mvc程式的View中使用IUrlHelper或TagHelper來生成頁面的超鏈接:
其生成原理是通過鏈接參數(如上圖所示的Controller和Action)去路由表中匹配,然後使用匹配結果中的第一個路由(可能會匹配到多個路由對象,具體內容在後續Area章節介紹)來生成鏈接。
更多路由信息及路由模板定義參考文檔:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.1
控制器
ASP.NET Core Mvc的Controller一般繼承Controller類型實現,基類Controller中包含了Mvc中常用的返回方法(如Json以及View等)以及用於數據存儲的ViewBag、ViewData、TempData。
Area
Area是Mvc應用中用來進行功能拆分或分組的一種方式,Area一般有自己的命名空間和目錄結構,一般Area的預設目錄結構如下:
ASP.NET Core Mvc和ASP.NET Mvc中的概念和用法基本上是一致的,但也存在一些區別:
1. Area下麵的Controller需要使用Area特性標明當前Controller屬於哪一個Area:
註:Area的目錄結構不是必須的,只需要通過特性標記的Controller都會被正確識別,但目錄結構的改變會導致無法找到View,關於View的查找路徑會在後續介紹。
2. Area的路由註冊也是在UseMvc方法中完成:
註:攜帶Area的路由模板需要放在前面,否則在生成通過IUrlHelper或TagHelper生成鏈接時,由於Controller以及action會匹配到沒有area的模板並使用該模板生成鏈接,導致area參數被忽略,而生成類似:/controller/action?area=area的結果(在生成Url時,ASP.NET Core會將多餘的路由參數放置到查詢字元串中)
View
View是基於Razor的HTML模板,Razor的詳細語法參考文檔:
https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-2.1
ASP.NET Core Mvc的View與ASP.NET Mvc中的使用方法基本一致,主要區別如下:
1. 引入了TagHelper,使用TagHelper可以讓View的代碼更接近Html。更多TagHelper信息參考文檔:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.1
2. Controller將參數傳輸到View的方法添加了ViewData特性,使用方法如下:
View中訪問被ViewData標記的方式:
更多詳情參考文檔:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-2.1#passing-data-to-views
3. 新增View組件:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
配置View的查找路徑:
ASP.NET Core可以在ConfigureServices方法中對RazorViewEngineOptions進行配置,如下圖所示,在預設查找位置基礎上添加了View以及AreaView的查找路徑:
模型綁定
模型綁定指的是ASP.NET Core Mvc將請求攜帶的數據綁定到Action參數的過程,ASP.NET Core Mvc的模型綁定數據源預設使用Form Values、Route Values以及Query Strings,所有值都以Name-Value的形式存在,模型綁定時主要通過參數名稱、參數名稱.屬性名稱、參數名稱[索引]等方式與數據源的Name進行匹配。
除了預設的數據源之外還可以從Http請求Header、Http請求Body甚至從依賴註入容器中獲取數據,要從這些數據源中獲取數據需要在相應參數上使用[FromHeader]、[FromBody]、[FromServices]特性。
如果需要獲取的數據在不同數據源中都存在時(Name存在於多個數據源中),還可以通過特性指明從哪一個數據源中獲取,如[FromForm]、[FromQuery]及[FromRoute]。
需要註意的是[FromBody]預設只支持Json格式的內容,如果需要支持其它格式,如XML需要添加相應的格式化器,添加方法如下圖所示:
更多模型綁定及驗證內容請參考文檔:https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.1
https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1
其中模型驗證的使用方式與ASP.NET Mvc一致,仍然是通過相應的驗證特性對模型或模型屬性進行標記。
Action的返回值與Json序列化
說完Action方法參數的綁定,再來看一下Action方法的返回類型,在ASP.NET Mvc中Controller提供了返回頁面內容的View方法以及返回Json內容的Json方法(當然還有文件、重定向、404等等其它內容返回方法,詳見Controller與ControllerBase類型)。
這裡有一個需要註意的地方是當使用Json方法返回一個對象實例時,預設使用首字母小寫的駝峰命名方式序列化實例的屬性名稱,如下圖所示:
訪問結果:
要使用大寫駝峰形式命名需要在配置Mvc服務時添加以下代碼來修改Json預設的序列化配置:
註:同樣的問題也存在於WebAPI的Ok方法以及Signalr的Json格式協議。
靜態資源
由於ASP.NET Core已經不再使用IIS請求管道,所以對於靜態資源的訪問來說需要在請求管道中添加相應的處理中間件來完成:
預設的無參UseStaticFiles方法將wwwroot目錄作為靜態資源存放目錄,如果要添加其它靜態內容目錄可以再次使用UseStaticFiles方法,並通過StaticFileOptions對目錄的訪問路徑以及實際路徑進行配置:
註:由於ASP.NET Core可以在Linux下運行,所以對於Linux來說路徑是大小寫敏感的,另外由於Windows和Linux類系統的路徑分隔符也不一致,所以為了保證路徑的統一,可以使用Path.Combine方法,該方法會根據操作系統的不同對路徑進行不同的處理。
另外對於css及js資源文件的打包、壓縮功能,最新版本(ASP.NET Core 2.1)的應用模板以及不會自動添加相關功能,需要在拓展工具中添加Bunlder& Minifier拓展:
然後通過右鍵js等資源文件來創建bundleconfig.json文件:
WebAPI
API控制器的創建
ASP.NET Core將Mvc和WebAPI進行了合併,它們的實現都直接或間接繼承了ControllerBase類型,只不過Mvc的基類Controller在ControllerBase的基礎上添加了一些用於處理View的功能。
用ASP.NET Core開發WebAPI時,Controller類型直接繼承ControllerBase。然後這個API的Controller就具有了基類的特性,返回一個結果僅需要使用Ok方法即可,如下圖所示:
然後在路由表中添加路由:
即可通過/api/default/index訪問到這個API:
但對於REST風格的API來說,它需要通過ApiController特性對Controller類型進行標記,並且通過Route特性來設置路由:
然後就可以通過HTTP謂詞來訪問API:
但要註意的是在ASP.NET Core中實現的REST風格的Controller,它不會再根據action方法的名稱來匹配謂詞,所以存在多個方法時會,那怕對方法進行了命名,但仍然會出現以下錯誤:
為瞭解決這個問題,需要通過添加謂詞特性解決:
模型綁定
WebAPI中的模型綁定與MVC存在一些區別,首先當使用ApiController標記Controller類型時,如果模型綁定驗證未通過,會直接返回400錯誤,不會執行Action方法(免去了使用!ModelState.IsValid進行判斷):
執行結果:
其次使用ApiController標記的Controller在執行模型綁定時會使用預設的推斷規則,該規則分別從Body、Form、Header、Query、Route、Services(它們分別對應FromBody、FromForm、FromHeader、FromQuery、FromRoute、FromServices特性)中推斷獲取數據並綁定,為什麼說推斷?
因為有一些特殊的規則:
1. FromBody用於複雜類型推斷,如果不是複雜類型(如int、string等)以及特殊的內置類型(IFormCollection文檔例子),則不會從Body中獲取數據,除非通過[FromBody]特性指明,例子如下:
請求結果:
當使用[FormBody]指明參數數據源後可以正常訪問:
註:當請求參數為簡單類型時,請求體內容類型需要為application/json,內容不能為Json字元串,使用參數值作為內容即可(上圖id沒有提供的異常並不是因為Json格式問題,而是沒有指明從body中獲取數據導致的)。
2. 只能存在一個參數從Body中獲取數據,如果出現多個參數時,只能保證一個參數從Body中獲取數據,其它參數需要指明獲取數據的位置:
該API的調用方式如下:
3. FromForm預設只推斷文件(IFormFile)及文件集合類型(IFormFileCollection),其餘類型預設均不會從Form中獲取。
4. 使用FromForm特性時會推斷multipart/form-data請求內容類型。
以上推斷行為可以通過如下配置禁用:
更多信息參考文檔:https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-2.1
SignalR
SignalR是用於客戶端伺服器實時通信的工具庫,從ASP.NET中就具有該功能,ASP.NET Core中的SignalR概念與用法與原來基本一致,但也存在一些區別:
1. 支持更多的客戶端,.Net客戶端、Java客戶端、Js客戶端以及非官方的C++客戶端、Swift客戶端。
2. 當鏈接SignalR並通過身份驗證後,SignalR會保存當前用戶鏈接SignalR的ID以及通過驗證後的用戶名,可以通過用戶名向用戶客戶端推送消息。
3. 在應用程式中可以通過IHubContext<HubType>方式,對SignalR上下文進行註入,並且可以直接通過該上下文推送數據給已經鏈接的客戶端,IHubContext<HubType>實際上是GlobalHost.ConnectionManager.GetHubContext<HubType>()的替代方式。
4. ASP.NET Core中通過app.UserSignalR以及route參數來映射一個Hub,每一個Hub擁有獨立的上下文,因此如果要使用IHubContext<HubType>來向客戶端推送信息,那麼必須準確註明Hub的類型,如下圖代碼應該使用IHubContext<ChatHub>,不能使用除ChatHub以外的類型(基類也不行)。
5. SignalR預設使用Json協議傳輸數據,預設情況下使用首字母小寫的駝峰命名方式序列化對象,要更改該預設行為需要通過一下代碼,替換預設的序列化行為:
6. ASP.NET Core的客戶端代碼(特指Js客戶端)有變更,需要對應版本使用。
關於更多SignalR內容請參考文檔:https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1
小結
本文主要介紹了ASP.NET Core中Mvc、WebAPI以及SignalR開發時與原來ASP.NET中的一些細小區別和新特性,整體來說ASP.NET Core與ASP.NET從使用方式上基本上是一致的,這也使得從ASP.NET遷移到ASP.NET Core變得更加容易,但可能因為這些細小的問題往往會向代碼中埋入一些坑,所以特別編寫了本文來解釋這些問題。
總的來說ASP.NET Core的文檔相當齊全,本文中大部分內容實際都是文檔中提到的,所以建議大家在使用ASP.NET Core開發時,首先第一步就是熟讀文檔,避免遺漏細節。希望本篇文章對大家有幫助(*^_^*)
參考:
https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-2.1
本文鏈接:https://www.cnblogs.com/selimsong/p/10047321.html