ASP.NET與ASP.NET Core很相似,但它們之間存在一些細微區別以及ASP.NET Core中新增特性的使用方法,在此以前也寫過一篇簡單的對比文章ASP.NET MVC應用遷移到ASP.NET Core及其異同簡介,但沒有進行深刻的分析和介紹,在真正使用ASP.NET Core進行開發時,若是忽略這些細節可能會出現奇怪的問題,特此將這些細節進行分享。
本文主要內容有:css
注:本文基於ASP.Net Core 2.1版本,.Net Core SDK版本須要2.1.401+。長篇預警( ╯□╰ )html
ASP.NET與ASP.NET Core之間最大區別之一就是內置了依賴注入機制,雖然ASP.NET中也有DI機制,但沒有內置容器,通常都須要使用第三方的容器來提供服務,另外依賴注入的概念也不像ASP.NET Core中這樣無處不在。
簡單來講依賴注入的目的是爲了讓代碼解耦以提升代碼的可維護性,同時也要求代碼設計符合依賴致使原則使得代碼更加靈活,而其原理實際上就是在應用程序中添加一個對象容器,在應用初始化時將實際的服務「放」到容器中,而後當須要相應服務時從容器中獲取,由容器來組裝服務。web
ASP.NET Core的Startup(注:Startup僅僅只是約定名稱,實際使用是在Program類型中建立 WebHost時使用的),該類型中包含兩個方法分別是ConfigureServices和Configure,其中ConfigureServices的主要做用就是用來將服務「放」置到容器中json
代碼來自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1api
ASP.NET Core的默認容器僅提供了構造注入功能,若是須要使用屬性注入等功能或者在遷移時原有應用依賴於其它容器,那麼能夠經過使用第三方容器實現。
將默認容器替換爲其它容器僅需三步:
1. 將ConfigureServices方法的返回類型改成IServiceProvider。
2. 將ASP.NET Core中的服務註冊到第三方容器中。
3. 使用第三方容器實現IServiceProvider接口並返回。服務器
官方文檔以Autofac爲例,Autofac已經實現了ASP.NET Core服務註冊到Autofac容器中,以及Autofac容器的IServiceProvider接口封裝,僅需安裝Autofac以及Autofac.Extensions.DependencyInjection包便可。mvc
詳情參考: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-2app
雖然Controller在激活時是經過容器來獲取Controller的依賴(即構造方法須要的參數),在代碼運行的時候給人一種Controller是從容器中組裝的錯覺,可是實際上默認狀況下Controller的組裝過程不是直接由容器組裝的,若是要讓Controller從容器組裝,那麼在配置MVC服務時須要經過.AddControllerAsServices()方法將Controller註冊到容器中:ide
注:通常狀況下是否將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發生直接交互)。
在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因爲是基於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 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是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是基於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方法參數的綁定,再來看一下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文件:
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是用於客戶端服務器實時通訊的工具庫,從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