1. 程式人生 > >.NET Core多平臺開發體驗[1]: Windows

.NET Core多平臺開發體驗[1]: Windows

微軟在千禧年推出 .NET戰略,並在兩年後推出第一個版本的.NET Framework和IDE(Visual Studio.NET 2002,後來改名為Visual Studio),如果你是一個資深的.NET程式設計師,相信傳統的.NET應用的開發方式已經深深地烙印在你的腦子裡面。.NET Core打來了全新的開發體驗,但是開發方式的差異根本不足以成為你快速跨入.NET Core 世界的門檻,因為在.NET Core在很多方面比傳統的.NET Framework應用開發要簡單。為了消除很多尚未接觸過.NET Core的讀者對未知世界的恐懼,我們先通過幾個簡單的Hello World應用讓大家感受一下.NET Core全新的開發體驗。

目錄
一、安裝開發環境
二、利用命令列建立.NET Core程式
三、改造成一個ASP.NET Core應用
四、進一步改造成ASP.NET Core MVC應用

不管你是否已經接觸過.NET Core,我們相信你一定知道.NET Core是“跨平臺”的。較之傳統的.NET Framework應用只能執行在微軟自家的Windows平臺上,經過全新設計的.NET Core在誕生的時候就被注入了跨平臺的基因,通過.NET Core應用在無需經過任何更改的情況下就可以直接執行在Windows、Mac OS以及各種Linux Distribution(RHEL、Ubuntu、Debian、Fedora、CentOS和SUSE等)平臺上。除此之外,.NET Core針對Docker也提供了原生的支援,一個.NET Core應用可以同時執行在Windows Container和Linux Container上。我們接下里現在Windows平臺下感受一下.NET Core的開發體驗,不過在這之前先得構建一下開發環境。

一、安裝開發環境

.NET Core的官方站點(https://www.microsoft.com/net/core)提供了在各種平臺下安裝開發環境的介紹。總的來時,我們在不同的平臺下開發.NET Core應用都需要按照相應的SDK和IDE。針對Windows開發平臺來說,.NET Core 2.0.0 SDK可以通過上述這個站點直接下載。成功安裝SDK之後,我們在本地將自動擁有了.NET Core的執行時(CoreCLR)、基礎類庫以及相應的開發工具。

dotnet.exe是.NET Core SDK為我們提供的一個重要的命令列工具,我們在進行.NET Core應用的開發部署的時候將會頻繁地使用它。dotnet.exe提供了很多有用的命令,為了不“節外伸枝”,我們就不對它們作系統介紹了,如果後續章節涉及到相關命令的時候,我們再對它們作針對性的介紹。當.NET Core SDK安裝結束之後,我們可以執行dotnet命令來確認SDK是否安裝成功。如下圖1所示,我們執行dotnet --info

命令檢視當前安裝的.NET Core SDK的基本資訊,其中包含SDK的版本、執行時環境和共享框架宿主的版本資訊。

1-1

高效的開發自然離不開一個優秀的IDE,在這方面作為一個.NET程式設計師是幸福的,因為我們擁有宇宙第一個開發神器Visual Studio。雖然Visual Studio Code也不失為一個優秀的IDE,如果在絕大部分情況下Windows還是主要的開發環境,我個人還是推薦使用Visual Studio。當我們在敲這行文字的時候,Visual Studio的最新版本為2017(15.3)。順便說一下,Visual Studio已經提供了Mac版本。

Visual Studio Code是一個完全免費並且提供全平臺支援(Windows、Mac和Linux)的IDE,我們可以直接在其官網(https://code.visualstudio.com/)上下載。Visual Studio 2017提供了社群版(Community)、專業版(Professional)和企業版(Enterprise),其中社群版是免費的,專業版和企業版需要付費購買。Visual Studio的官網地址為https://www.visualstudio.com/。

除了Visual Studio和Visual Studio Code,我們還可以一款叫做Rider的IDE來開發.NET Core應用。Rider是著名的JetBrains公司開發的一款專門針對.NET的IDE,我們可以利用它來開發ASP.NET、.NET Core、Xmarin以及Unity應用。和Visual Studio Code一樣,Rider同樣也是個跨平臺的IDE,我們可以同時在Windows、Max OS X以及各種桌面版本的Linux Distribution上使用它。不過這不是一款免費的IDE,對它感興趣的朋友可以在官方站點(https://www.jetbrains.com/rider/)下載30天試用版。

二、利用命令列建立.NET Core程式

通過.NET Core SDK在本地安裝的dotnet 工具提供了基於預定義“腳手架(Scaffolding)”建立初始應用的命令(new)。如果需要開發某種型別的.NET Core應用,我們一般不會從第一行程式碼寫起,而是利用這個命令幫助我們建立一個具有初始結構的應用。除此之外,在開發過程中如果需要新增某種型別的檔案(比如各種型別的配置檔案、MVC的檢視檔案等),我們也可以利用該命令來完成,通過這種方式新增的檔案具有預定義的初始內容。.NET Core SDK在安裝的時候為我們提供了一系列預定義的腳手架模板,我們可以按照如下圖所示的方式執行命令列“dotnet new list”列出當前安裝的腳手架模板。

1-2

上圖列出就是NET Core 2.0.0 SDK安裝後提供的16個預定義的腳手架模板,這些模板大致分為Project TemplateItem Template兩種類,前者為我們建立一個初始專案,後者則在一個現有專案中針對某種專案元素新增一個或者多個對應的檔案。對於細心的讀者,可以從上圖中看到dotnet new命令具有一個--type引數,該引數具有三個預定義的選項(project、item和other),其中前兩個分別對應著Project和Item這兩種模板型別。

如果這些預定義的腳手架模板不能滿足我們的需求,我們還可以根據自身的需要建立自定義的Project或者Item模板,至於自定義模板的該如何定義,我們就不在這裡贅言介紹了,有興趣的讀者朋友可以參考.NET Core官方文件(https://docs.microsoft.com/en-us/dotnet/core/tools/custom-templates)。我們建立的自定義模板最終體現為一個NuGet包,我們可以通過執行dotnet new -i或者dotnet new --install命令對其進行安裝。除此之外,對應已經安裝的模板,我們可以通過執行dotnet new -u或者dotnet new --uninstall命令解除安裝。

接下來我們利用dotnet new命令(dotnet new console -n helloworld)按照如下圖所示的方式建立一個名為“helloworld”的控制檯程式。和傳統的.NET Framework應用,一個針對C#的.NET Core專案依然由一個對應的.csproj檔案來定義,下圖所示的helloworld.csproj就是這麼一個檔案。

1-3

對於傳統的.NET Framework應用來說,即使是一個空的C#專案,定義該專案的.csproj檔案在內容和結構上顯得比較複雜。這個.csproj檔案的結構並不是為一般的開發者設計的,我們也不會直接編輯這個檔案,而是利用Visual Studio間接地修改它。但是對於一個.NET Core應用來說,這個.csproj檔案的結構變得相對簡單並清晰了一些,以至於一般的開發人員可以直接編輯它。對於上面我們執行腳手架命令建立的控制檯程式,定義專案的這個helloworld.csproj檔案的完整內容如下。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
</Project>

如上面的程式碼片段所示,這個helloworld.csproj是一個根節點為<Project>的XML檔案,與專案相關的屬性可以根據分組定義在相應的<PropertyGroup>節點下。對於這個helloworld.csproj檔案來說,它實際上只定義了兩個屬性,分別是通過<OutputType>和<TargetFramework>節點表示的編譯輸出型別和目標框架型別。由於我們建立的是一個針對.NET Core 2.0的可執行控制檯應用,所以目標框架為“netcoreapp2.0”,編譯輸出為Exe(對於Self Contained釋出模式)。

我們執行的dotnet new命令列除了幫助我們建立一個空的控制檯程式之外,還會幫助我們生成一些初始化程式碼,這就是專案目錄下的這個Program.cs檔案的內容。如下所示的程式碼片段給出了定義在這個檔案的整個C#程式碼的定義,我們可以看到它定義了代表程式入口點的Main方法,並在這個方法中將字串“Hello World”列印在控制檯上。

using System;    
namespace helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

雖然很簡單,但是我們通過執行腳手架命令列創建出來的是一個完成的.NET Core控制檯應用,所以我們可以在無需對其作任何修改的情況下直接對它進行編譯和執行,針對.NET Core應用的編譯和運行同樣是利用這個dotnet.exe命令列來完成的。如下圖所示,在進入當前專案所在目錄之後,我們執行dotnet build命令對這個控制檯應用實施編譯,由於預設採用Debug編譯模式,所以編譯生成的程式集(helloworld.dll)會儲存在\bin\debug\目錄下。除此之外,針對不同目標框架編譯生成的程式集是不同的,所以最終生成的程式集會採用基於目標框架的目錄結構進行組織,所以最終生成的這個程式集被儲存在“\bin\Debug\netcoreapp2.0\”目錄下。

1-4

編譯後的控制檯程式可以直接通過執行dotnet run命令執行。如下圖所示,當我們在專案目錄下執行dotnet run命令後,編譯後的程式隨被執行,程式入口Main方法中指定的“Hello World”字串被直接列印在控制檯上。其實當我們執行dotnet run命令啟動程式之前無需顯示執行dotnet build對原始碼實施編譯,因為該命令會自動觸發編譯操作。

1-5

三、改造成一個ASP.NET Core應用

我們在上面利用dotnet new命令建立了一個簡單的控制檯程式,接下來我們將它改造成一個ASP.NET Core應用。一個ASP.NET Core應用構建在ASP.NET Core框架之上,後者利用一個管道式的構建完成了對HTTP請求的監聽、接收、處理和最終的響應。ASP.NET Core管道由一個伺服器和若干中介軟體構成,當宿主(Host)程式啟動之後,該管道被成功構建出來並利用伺服器開始HTTP請求的監聽。

從程式設計層面來看,ASP.NET Core管道的構建主要涉及WebHostWebHostBuilder這兩個物件。我們可以根據命名將WebHost理解為Web應用的宿主,WebHost是由WebHostBuilder構建出來的。一般來說,我們會先建立一個WebHostBuilder物件,並將最終管道構建所需的各項設定通過相應的方法(絕大部分是擴充套件方法)註冊到它上面。註冊完成之後,我們直接利用這個WebHostBuilder創建出對應的WebHost,當後者被啟動的時候,整個管道會根據我們預定義的設定被構建出來。

接下來我們直接利用Visual Studio 2017開啟上面這個helloworld.csproj專案檔案。為了在程式性中使用到上述這兩個物件,我們自然先得具有對應程式集的引用。對於.NET Core應用來說,所有的程式集都會封裝到相應的NuGet包中進行分發,如果需要消費某個框架或者類庫,我們都需要預先安裝相應的NuGet包。至於NuGet包的安裝,我們有很多的方式可以選擇。

安裝NuGet包

WebHostBuilder所在的程式集包含在Microsoft.AspNetCore.Hosting這個NuGet包中,接下來我們就以它為例介紹若干中不同的NuGet包的安裝方式。如果使用Visual Studio 2017來開發.NET Core應用,我們最常使用的是由IDE提供的視覺化NuGet安裝方式。如果按照這種安裝方式,我們只需要在“解決方案管理器(Solution Explorer)”視窗中右鍵選擇對應的專案或者專案下的“依賴(Dependencies)”節點,並在彈出的上下文選單中選擇“管理NuGet包(Manage NuGet Packages)”選項就可以開啟如下圖所示的“NuGet包管理視窗”。

1-6

如上圖所示,“NuGet包管理視窗”具有三個標籤頁,其中“安裝(Installed)”和“更新(Updates)”會列出當前專案已經安裝和可以升級的NuGet包。如果我們需要按照一個新的NuGet包,我們需要選擇第一個標籤頁,並在左上角的文字框中輸入需要安裝的NuGet包的全名或者全名的部分文字,與之相關的NuGet包將會篩選出來,如果目標NuGet包正好在該列表中,我們只需要選擇它並點選右側的“安裝(Install)”按鈕即可。

除了採用上述這種完全視覺化的方式來安裝NuGet包,Visual Studio還提供了一種命令列的安裝方式。包括安裝在內的NuGet包管理命令是在Visual Studio的“包管理器控制檯(Package Manager Console)”視窗中輸入並執行的,我們可以通過選單“工具(Tools)>選項(Options)>NuGet包管理器(NuGet Package Manager)>包管理器控制檯(Package Manager Console)”開啟如下圖所示的這個視窗。如下圖所示,我們通過輸入並執行NuGet包安裝命令“Install-Package Microsoft.AspNetCore.Hosting -Version 2.0.0”安裝版本為2.0.0的NuGet包“Microsoft.AspNetCore.Hosting”。

1-7

除了上面介紹的這兩種在Visual Studio開發環境中提供的NuGet包的安裝放之外,我們已經很熟悉的這個dotnet命令列工具同樣提供了安裝NuGet包的支援。具體來說,我們可以執行dotnet add package命令安裝指定的NuGet包。如下圖所示,在確保當前為目標專案所在目錄的前提下,我們所需的NuGet包同樣可以通過執行“dotnet add package Microsoft.AspNetCore.Hosting --version 2.0.0”命令來完成。

1-8

有的情況下我們往往只知道某個需要使用的型別名稱而忘記了所在NuGet包的名字,如果你使用了Visual Studio 2017,可以藉助它提供的智慧提示功能來安裝對應的NuGet包。很多人都體現過Visual Studio針對名稱空間的自動補齊特性,當我們在C#編輯視窗直接輸入一個尚未匯入名稱空間的型別名稱的時候,一旦我們將滑鼠落在該型別上面的時候,Visual Studio會自動出現如下圖所示的“燈泡”圖示,點選該圖示之後會出現一組候選的名稱空間(這組候選名稱空間選單也可以通過快捷鍵Ctrl+Alt+F10開啟)。

1-9

這個特性在Visual Studio 2017上做了進一步改進。如果我們輸入了某個型別的名稱,但是所在的NuGet包尚未被安裝,Visual Studio可以利用這個特性智慧地提示這個缺失的NuGet包的名稱。如下圖所示,在尚未安裝“Microsoft.AspNetCore.Hosting”這個NuGet包的情況下,我們在C#編輯視窗中輸入WebHostBuilder這個型別,Visual Studio會利用類似的特性提示我們安裝缺失的NuGet包。

1-10

對於對上述的眾多NuGet包的安裝方式,它們最終的目的實際上就是在描述當前專案的.csproj問檔案中新增一個針對指定NuGet包的引用而已。如下所示的程式碼片段代表“Microsoft.AspNetCore.Hosting”這個NuGet包被成功安裝後的內容,可以看出針對某個NuGet包的引用總是對應著.csproj檔案中的某個< PackageReference>節點。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.0.0" />
  </ItemGroup>
</Project>

既然安裝NuGet包的最終目的就是在所在專案的.csproj檔案中新增一條對應的< PackageReference>節點,那麼我們完全可以直接通過修改此檔案的方式來完整針對NuGet包的安裝。在過去,如果我們想直接利用Visual Studio編輯某個專案對應的.cspro檔案,我們必須先採用如下圖(左圖和中圖)的方式將對應的專案解除安裝。對於.NET Core應用開發來說,直接編輯專案檔案已經變成了意見司空見慣的事情,所以Visual Studio 2017允許我們按照下圖(右圖)所示的方式直接對專案檔案進行編輯。

1-11

對於.NET Core來說,提供API的程式集總是通過相應的NuGet包來提供,所以NuGet包的安裝成為了我們最為頻繁的操作之一。除此之外,我們在IDE上也有了更多的選擇,所以微軟提供了眾多NuGet包的管理方式供我們在不同的開發環境中選擇。綜上所述,我們可以通過如下的方式進行NuGet包的安裝:

  • 利用Visual Studio的NuGet包管理器(NuGet Package Manager)進行視覺化安裝。
  • 在Visual Studio提供的包管理器控制檯(Package Manager Console)以命令列的方式安裝NuGet包。
  • 通過執行dotnet add package以命令列的形式安裝NuGet包。
  • 通過修改定義專案的.csproj檔案安裝的方式安裝NuGet包。

在介紹瞭如何安裝NuGet包之後,我們回到本行最初的話題:如何將通過腳手架命令建立的控制檯應用轉化成一個ASP.NET Core應用。我們在前面已經提到過,ASP.NET Core應用建立在由一個伺服器和若干中介軟體組成的管道上,最初對HTTP請求的監聽以及最終對該請求的響應都由伺服器完成。微軟為我們提供了若干原生的伺服器供我們選擇,如果對應用具有跨平臺的要求,我們可以選擇一個名為KestrelHttpServer的伺服器。該伺服器型別定義在NuGet包“Microsoft.AspNetCore.Server.Kestrel”中,所以我們還需要安裝這個NuGet包。

註冊伺服器與中介軟體

在安裝了所需的NuGet包(“Microsoft.AspNetCore.Hosting”和“Microsoft.AspNetCore.Server.Kestrel”),我們對定義在Program.cs中的Main方法做了如下的改造,這應該算是一個最為簡單的ASP.NENT Core應用了。我們首先建立了一個WebHostBuilder物件並呼叫其擴充套件方法UseKestrel註冊了上述的這個型別為KestrelHttpServer的伺服器。接下來我們呼叫了WebHostBuilder的Configure方法,該方法接受一個Action<IApplicationBuilder>型別的委託物件作為引數,我們利用這個委託物件呼叫IApplicationBuilder的Run方法註冊了一箇中間件,後者從事的唯一操作就是在響應中寫入了一個內容為“Hello World”的字串。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace helloworld
{
    class Program
    {
        static void Main()
        {
             new WebHostBuilder()                    
                .UseKestrel()
                .Configure(app => app.Run( async context => await context.Response.WriteAsync("Hello World")))
              .Build()
              .Run();
        }
    }
}

在將所需的伺服器和中介軟體註冊到建立的WebHostBuilder物件之後,我們呼叫其Build方法建立了對應的WebHost物件, 當我們呼叫後者的Run方法之後,真正的ASP.NET Core管道被構建出來。由於註冊的KestrelHttpServer伺服器預設採用5000作為監聽埠,所以程式在啟動之後將繫結在這個埠監聽抵達的HTTP請求。如果利用瀏覽器訪問http://localhost:5000,我們會得到由註冊中介軟體寫入的“Hello World字串”。

1-12

如果預設使用的5000埠不可用,或者不希望使用這個預設的埠,我們還可以呼叫WebHostBuilder的擴充套件方法UseUrls註冊新的監聽地址。值得一提的是,我們可以同時指定多個監聽地址,下面的程式碼片段就為KestrelHttpServer分別註冊了兩個埠號分別為6666和8888的監聽地址。

new WebHostBuilder()    
    .UseUrls("http://localhost:6666", "http://localhost:8888")
    .UseKestrel()
    .Configure(app => app.Run(async context => await context.Response.WriteAsync("Hello World")))
    .Build()
    .Run();

四、進一步改造成ASP.NET Core MVC應用

對於我們在上面一節建立的這個極簡ASP.NET Core應用來說,它對應的管道由一個伺服器和一箇中間件組成,前者的型別為KestrelHttpServer,後者則將每個請求的響應內容統一設定為“Hello World”字串。接下來我們對該應用做進一步地改造,將它轉變成一個ASP.NET Core MVC應用。整個ASP.NET Core MVC框架建立在一個名為RouterMiddleware的中介軟體上,它利用該中介軟體提供的路由功能實現了請求URL與目標Controller型別以及Action方法之間的對映,並再此基礎上實現了諸如Controller的啟用、Action方法的執行以及View的呈現等操作。

整個ASP.NET Core MVC框架實現在“Microsoft.AspNetCore.Mvc”這個NuGet包中,所以我們先得將它安裝到我們的控制檯專案上,然後才能進行鍼對ASP.NET Core MVC的程式設計。不過在這之前我們需要了解一個關於“依賴注入(Dependency Injection)”。ASP.NET Core框架內建了一個原生的依賴注入框架,後者利用一個DI容器提供管道在構建以及進行HTTP請求處理過程中所需的所有服務,而這些服務需要在應用啟動的時候被預先註冊。對於ASP.NET Core MVC框架來說,它在處理HTTP請求的過程中所需的一系列服務同樣需要預先註冊。對這個概念有了基本的瞭解之後,相信讀者朋友們對如下所示的程式碼就容易理解了。

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

namespace helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            new WebHostBuilder()
                .UseKestrel()
                .ConfigureServices(svcs => svcs.AddMvc())
                .Configure(app => app.UseMvc())
                .Build()
                .Run();
        }
    }

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

如果說上面一節建立的屬於最簡單的ASP.NET Core應用的話,那麼通過上面這段程式碼建立的恐怕就是一個簡單的ASP.NET Core MVC應用了。我們在這個程式中呼叫了WebHostBuilder的方法ConfigureServices(這是一個擴充套件方法,其引數型別為Action<IServiceCollection>)註冊ASP.NET Core MVC框架所需的服務,具體來說這些服務是通過呼叫IServiceCollection介面的擴充套件方法AddMvc來完成的。在針對Configure方法的呼叫中,我們呼叫IApplicationBuilder的擴充套件方法UseMvc註冊了RouterMiddleware中介軟體以及針對ASP.NET Core MVC的路由處理器,後者接收路由分發的HTTP請求和路由引數,並在此基礎上實現對Controller的啟用、Action方法的執行以及View的呈現等操作。

HelloController是我們定義的Controller型別。按照約定,所有的Controller型別名稱都應該以“Controller”字元作為字尾。與之前版本的ASP.NET MVC不同的是,ASP.NET Core MVC下的Controller型別並不要求強制繼承某個基類。在HelloController中,我們定義了一個唯一無參Action方法SayHello,該方法直接返回一個內容為“Hello World”的字串。我們在這個方法上利用HttpGetAttribute註冊了一個模板為“/hello”的路由,意味著請求地址為“/hello”的GET請求最終會被路由到這個Action方法上,後者執行的結果將作為請求的響應內容。所以啟動該程式後使用瀏覽器訪問地址“http://localhost:5000/hello”,我們依然會得到如圖1-13所示的輸出結構。

1-13

引入View

上面這個程式並沒有涉及View,所以算不上一個典型的ASP .NET Core MVC,接下來我們對它做進一步改造。在前面介紹如何安裝NuGet包的時候,我們曾經檢視過定義專案的 .csproj檔案的內容,實際上這是一個以<Project>作為根節點的XML檔案。作為根節點的<Project>元素具有一個Sdk的屬性表示當前專案針對的SDK,不同的SDK在編譯釋出等方面對專案提供了不同的支援。當我們安裝了.NET Core SDK的時候,這些針對專案的SDK將被安裝在“%programfiles% \dotnet\sdk\{version}\Sdks”目錄下(如下圖所示)。

1-14

對於上面這個通過腳手架命令列建立的控制檯應用來說,它預設採用的SDK為“Microsoft.NET.Sdk”。如果我們需要為這個應用新增View,這個SDK並不能提供針對View的動態編譯功能(在執行時動態編譯使用到的View,而不是在部署的時候將所有View進行預編譯),所以我們按照如下的方式編輯.csproj檔案將SDK切換為“Microsoft.NET.Sdk.Web”。實際上不論是我們利用Visual Studio還是命令列建立的ASP.NET Core應用,專案都會採用這個SDK。

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
  </ItemGroup>
</Project>

在對SDK作了相應修改之後,我們對現有的程式作了如下的改造。當編譯器在對View進行動態編譯的時候,需要按照預定義的路徑去定位定義View的.cshtml檔案,這些預定義的候選路徑都是相對路徑,所以我們需要預先指定一個基礎路徑,該路徑可以通過呼叫WebHostBuilder的擴充套件方法UseContentRoot來指定。如下面的程式碼片段所示,我們將當前路徑(當前專案的根目錄)作為這個基礎路徑。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System.IO;

namespace helloworld
{
    class Program
    {
        static void Main()
        {
            new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .ConfigureServices(svcs => svcs.AddMvc())
                .Configure(app => app.UseMvc())
                .Build()
                .Run();
        }
    }

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

為了讓HelloController具有View呈現的能力,我們讓它派生於基類Controller。Action方法SayHello的返回型別被修改為IActionResult介面,後者表示Action方法執行的結果。除此之外,我們為該方法定義了一個表示姓名的引數name,通過HttpGetAttribute特性註冊的路由模板(“/hello/{name}”)中具有與之對應的路由引數。換句話說,滿足該路由規則的請求URL攜帶的命名將自動繫結為該Action方法的name引數。具體在SayHello方法中,我們利用ViewBag將程式碼姓名的name引數的值傳遞給將要呈現出來的View,該View正式執行View方法返回的結果。

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

1-15

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

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

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

1-16

利用Startup註冊服務和中介軟體

對於幾乎所有的ASP.NET Core應用來說,應用啟動過程中必須完成的初始化操作都包括服務與中介軟體的註冊。在上面演示的例項中,我們都是直接呼叫WebHostBuilder的ConfigureServices和Configure方法來完成針對服務和中介軟體的註冊,但是在大部分真實的開發場景中,我們一般會將這兩種型別的註冊定義在一個單獨的型別中。按照約定,我們通常會將這個型別命名為Startup,比如我們演示例項中針對ASP.NET Core MVC的服務註冊和中介軟體註冊就可以放在如下定義的這個Startup類中。

public class Startup
{
    public void ConfigureServices(IServiceCollection svcs)
    {
        svcs.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

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

new WebHostBuilder()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseKestrel()                     
    .UseStartup<Startup>()
    .Build()
    .Run();

.NET Core多平臺開發體驗