歡迎光臨
每天分享高質量文章

ASP.NET Core開髮指南

ASP.NET與ASP.NET Core很類似,但它們之間存在一些細微區別以及ASP.NET Core中新增特性的使用方法,在此之前也寫過一篇簡單的對比文章ASP.NET MVC應用遷移到ASP.NET Core及其異同簡介,但沒有進行深入的分析和介紹,在真正使用ASP.NET Core進行開發時,如果忽略這些細節可能會出現奇怪的問題,特此將這些細節進行分享。

 

註:本文基於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

 

替換預設的依賴註入容器

 

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

 

使用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

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

 

該管道主要包含了錯誤處理(開發環境顯示異常信息,其它環境跳轉錯誤頁面)中間件、靜態檔案處理中間件以及Mvc中間件。

 

更多中間件可參考文件:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index

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

 

控制器

 

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

 

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

 

3、新增View組件:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components

 

配置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

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation

 

其中模型驗證的使用方式與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/

SignalR

SignalR是用於客戶端服務器實時通信的工具庫,從ASP.NET中就具有該功能,ASP.NET Core中的SignalR概念與用法與原來基本一致,但也存在一些區別:

 

1、支持更多的客戶端,.Net客戶端、Java客戶端、Js客戶端以及非官方的C++客戶端、Swift客戶端。

 

2、當鏈接SignalR並通過身份驗證後,SignalR會儲存當前用戶鏈接SignalR的ID以及通過驗證後的用戶名,可以通過用戶名向用戶客戶端推送訊息。

 

3、在應用程式中可以通過IHubContext方式,對SignalR背景關係進行註入,並且可以直接通過該背景關係推送資料給已經鏈接的客戶端,IHubContext實際上是GlobalHost.ConnectionManager.GetHubContext()的替代方式。

 

4、ASP.NETCore中通過app.UserSignalR以及route引數來映射一個Hub,每一個Hub擁有獨立的背景關係,因此如果要使用IHubContext來向客戶端推送信息,那麼必須準確註明Hub的型別,如下圖代碼應該使用IHubContext,不能使用除ChatHub以外的型別(基類也不行)。

 

 

5、SignalR預設使用Json協議傳輸資料,預設情況下使用首字母小寫的駝峰命名方式序列化物件,要更改該預設行為需要通過一下代碼,替換預設的序列化行為:

 

 

6、ASP.NET Core的客戶端代碼(特指Js客戶端)有變更,需要對應版本使用。

 

關於更多SignalR內容請參考文件:

https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction

小結

本文主要介紹了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/

赞(0)

分享創造快樂