1. 程式人生 > >[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Windows [下篇]

[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Windows [下篇]

由於ASP.NET Core框架在本質上就是由伺服器和中介軟體構建的訊息處理管道,所以在它上面構建的應用開發框架都是建立在某種型別的中介軟體上,整個ASP.NET Core MVC開發框架就是建立在用來實現路由的EndpointRoutingMiddleware和EndpointMiddleware中介軟體上。ASP.NET Core MVC利用路由系統為它分發請求,並在此基礎上實現針對目標Controller的啟用、Action方法的選擇和執行,以及最終對於執行結果的響應。在介紹的例項演示中,我們將對上面建立的ASP.NET Core作進一步改造,使之轉變成一個MVC應用。

一、註冊服務與中介軟體

ASP.NET Core框架內建了一個原生的依賴注入框架,該框架利用一個依賴注入容器提供管道在構建以及請求處理過程中所需的服務,而這些服務需要在應用啟動的時候被預先註冊。對於ASP.NET Core MVC框架來說,它在處理HTTP請求的過程中所需的一系列服務同樣需要預先註冊。對這個概念有了基本的瞭解之後,相信讀者朋友們對如下所示的程式碼就容易理解了。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace helloworld
{
    class Program
    {
        static void Main()
        {
            Host.CreateDefaultBuilder()
                .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder
                    .ConfigureServices(servicecs => servicecs
                        .AddRouting()
                        .AddControllersWithViews())
                    .Configure(app => app
                        .UseRouting()
                        .UseEndpoints(endpoints => endpoints.MapControllers())))
                .Build()
                .Run();
        }
    }
}

整個ASP.NET MVC框架建立在EndpointRoutingMiddleware和EndpointMiddleware中介軟體構建的路由系統上,這兩個中介軟體採用“終結點(Endpoint)對映”的方式實現針對HTTP請求的路由。這裡所謂的終結點可以視為應用程式提供的針對HTTP請求的處理器,這兩個終結點通過預先設定的規則將具有某些特徵的請求(比如路徑、HTTP方法等)對映到對應的終結點,進而實現路由的功能。對於一個MVC應用程式來說,我們可以將定義在Controller型別中的Action方法視為一個終結點,那麼路由對映最終體現在HTTP請求與目標Action方法的對映上。

如上面的程式碼片段所示,我們先後呼叫了IApplicationBuilder介面的UseRouting和UseEndpoints擴充套件方法註冊了EndpointRoutingMiddleware和EndpointMiddleware中介軟體。在呼叫UseEndpoints方法的時候,我們利用指定的Action<IEndpointRouteBuilder>委託物件呼叫了IEndpointRouteBuilder介面的MapControllers擴充套件方法完成了針對定義在Controller型別中所有Action方法的對映。

由於註冊的中介軟體具有對其他服務的依賴,我們需要預先將這些服務註冊到依賴注入框架中。依賴服務的註冊通過呼叫IWebHostBuilder的ConfigureServices方法來完成,該方法的引數型別為Action<IServiceCollection>,新增的服務註冊就儲存在IServiceCollection介面表示的集合中。在上面的演示程式中,兩個中介軟體依賴的服務是通過呼叫IServiceCollection介面的AddRouting和AddControllersWithViews方法進行註冊的。

如下所示的HelloController是我們定義的Controller型別。按照約定,所有的Controller型別名稱都應該以“Controller”字元作為字尾。與之前版本的ASP.NET MVC不同,ASP.NET Core MVC下的Controller型別並不要求強制繼承某個基類。我們在HelloController中定義了一個唯一的Action方法SayHello,該方法直接返回一個內容為“Hello World”的字串。

public class HelloController
{
    [HttpGet("/hello")]
    public string SayHello() => "Hello World.";
}

我們在Action方法SayHello上通過標註的HttpGetAttribute特性註冊了一個模板為“/hello”的路由,意味著請求地址為“/hello”的GET請求最終會被路由到這個Action方法上,而該方法執行的結果將作為請求的響應內容。所以啟動該程式後使用瀏覽器訪問地址“http://localhost:5000/hello”,我們依然會得到如下圖所示的輸出結果。

二、引入檢視

上面這個程式並沒有涉及檢視,所以算不上一個典型的MVC應用,接下來我們對它做進一步改造。為了讓HelloController具有檢視呈現的能力,我們讓它派生於基類Controller。Action方法SayHello的返回型別被修改為IActionResult介面,它表示Action方法執行的結果。我們為該方法定義了一個表示姓名的引數name,通過HttpGetAttribute特性註冊的路由模板(“/hello/{name}”)中具有與之對應的路由引數。換句話說,滿足該路徑模式的請求URL攜帶的姓名將自動繫結到該Action方法的name引數上。在SayHello方法中,我們利用ViewBag將代表姓名的name引數值傳遞給呈現的檢視,該方法最終呼叫View方法返回當前Action方法對應的ViewResult物件。

public class HelloController : Controller
{
    [HttpGet("/hello/{name}")]
    public IActionResult SayHello(string name)
    {
        ViewBag.Name = name;
        return View();
    }
}

由於我們呼叫View方法時沒有顯式指定檢視的名稱,所以檢視引擎會將當前Action的名稱(“SayHello”)作為檢視的名稱。如果該檢視還沒有經過編譯(部署時針對View的預編譯,或者在這之前針對該View的動態編譯),檢視引擎將從若干候選的路徑中讀取對應的.cshtml 檔案進行編譯,其中首選的路徑為“{ContentRoot}\Views\{ControllerName}\{ViewName}.cshtml”。為了迎合檢視引擎定位檢視檔案的規則,我們需要將SayHello對應的檢視檔案(SayHello.cshtml)定義在目錄“\Views\Hello\”下。

如下所示的就是SayHello.cshtml這個檔案的內容,這是一個針對Razor引擎的檢視檔案。從檔案的副檔名(.cshtml)我們看出可以這樣的檔案可以同時包含HTML標籤和C#程式碼。總的來說,檢視檔案會在服務端生成最終在瀏覽器呈現出來的HTML,我們可以在這個檔案中直接提供原樣輸出的HTML標籤,也可以內嵌一段動態執行的C#程式碼。雖然Razor引擎對View檔案的編寫制定了嚴格的語法,但是我個人覺得沒有必要在Razor語法上花太多的精力,因為Razor語法的目的就是讓我們很“自然”地將動態C#程式碼和靜態HTML標籤結合起來,並最終生成一份完整的HTML文件,因此它的語法和普通的思維基本是一致。比如下面這個View最終會生成一個完整的HTML文件,其主體部分只有一個<p>標籤。該標籤的內容是動態的,因為包含利用ViewBag從Controller傳進來的姓名。

<html>
  <head>
    <title>Hello World</title>
  </head>
  <body>
    <p>Hello, @ViewBag.Name</p>
  </body>
</html>

再次執行該程式後,我們利用瀏覽器訪問地址“http://localhost:5000/hello/foobar”。由於請求地址與Action方法SayHello上的路由規則相匹配,所以路徑攜帶的姓名(foobar)會繫結到該方法的name引數上,所以我們最終將在瀏覽器上得到如下圖所示的輸出結果。

三、使用Startup型別

任何一個ASP.NET Core應用在初始化的時候都會根據請求處理的需求註冊對應的中介軟體。在前面演示的例項中,我們都是直接呼叫IWebHostBuilder的Configure擴充套件方法來註冊所需的中介軟體,但是在大部分真實的開發場景中我們一般會將中介軟體以及依賴服務的註冊定義在一個單獨的型別中。按照約定,我們通常會將這個型別命名為Startup,比如我們演示例項中針對服務和中介軟體的註冊就可以放在如下定義的這個Startup類中。

public class Startup
{
    public void ConfigureServices(IServiceCollection services) => services
        .AddRouting()
        .AddControllersWithViews();

    public void Configure(IApplicationBuilder app) => app
        .UseRouting()
        .UseEndpoints(endpoints => endpoints.MapControllers());
}

如上面的程式碼片段所示,我們不需要讓Startup類實現某個預定義的介面或者繼承某個預定義基類,所採用的完全是一種基於“約定”的定義方式。隨著對ASP.NET Core框架認識的加深,我們會發現這種“約定優於配置”的設計廣泛地應用在整個框架之中。按照約定,服務註冊和中介軟體註冊分別實現在ConfigureServices和Configure方法中,它們的第一個引數型別分別為IServiceCollection和IApplicationBuilder介面。由於已經將兩種核心的操作轉移到了Startup型別中,所以我們需要註冊該型別。Startup型別可以呼叫IWebHostBuilder介面的UseStartup<TStartup>擴充套件方法進行註冊。

class Program
{
    static void Main()
    {
        Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.UseStartup<Startup>())
            .Build()
            .Run();
    }
}

我們在前面的內容中對.NET Core、ASP.NET Core以及ASP.NET Core MVC應用的程式設計作了初步的體驗,但是這僅僅限於我們熟悉的Windows平臺。作為一個號稱跨平臺的開發框架,我們有必要在其他作業系統平臺上體驗一下.NET Core開發的樂趣。

[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Windows [上篇]
[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Windows [中篇]
[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Windows [下篇]
[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Mac OS
[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Linux
[ASP.NET Core 3框架揭祕] 跨平臺開發體驗: Docker