Blazor Server App

by vivid 27. 十一月 2019 01:32

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N191121302
出刊日期: 2019/11/27

本站2018年《初探Blazor》文章中第一次介紹了「Blazor」,它是從這句話衍生出來的:

Browser + Razor = Blazor

「Blazor」是一個新的.NET網站框架(.NET web framework),以WebAssembly標準為基礎,可以取代以往使用JavaScript語言,改用C# / Razor語法與HTML標籤建立執行在瀏覽器上的用戶端應用程式,有了「Blazor」就可以讓程式設計師專注在一種程式語言,直接使用C# 語言進行全端開發(full stack web development)。隨著.net core 3.0的問市,現在我們可以真正開始使用ASP.NET Core Blazor 來開發互動式的用戶端網頁介面(Web UI)程式,當然《初探Blazor》文章中介紹的程式架構與語法也不太適用、需要改寫了。在這篇文章中,將要介紹如何在Visual Studio 2019開發工具中建立第一個Blazor應用程式。

 

ASP.NET Core Blazor

「Blazor」是一個開發用戶端網頁介面(Web UI)的框架,使用.NET程式庫(Library)與C#程式語言進行開發,可以享受到.NET帶來的效能、可靠性與安全性的好處,並且可跨Windows、Linux與macOS平台來運行。

「Blazor」應用程式目前分為兩類:

  • l 執行在用戶端瀏覽器,目前為預覽版(preview),參考下圖,以WebAssembly為基礎,使用C#撰寫的 *.razor檔案的程式都會編譯成組件(Assembly),這些組件與.NET runtime和相依的檔案將會下載到瀏覽器端執行。目前大部分的現代化瀏覽器都支援WebAssembly標準,但Microsoft Internet Explorer除外。

clip_image002

圖 1:執行在用戶端瀏覽器的WebAssembly。

  • l 執行在伺服端(Blazor Server),ASP.NET Core 3版及以上版本才有支援。Blazor Server應用程式運作方式是將Razor元件(Razor Component)裝載在伺服端上的ASP.NET Core app中執行,瀏覽器中的UI與伺服端將通過SignalR連線進行通訊,瀏覽器中的UI事件觸發時,將通知伺服端做對應處理,再於瀏覽器更新UI。所有的現代化瀏覽器都可支援Blazor Server,Microsoft Internet Explorer需要11版以上搭配一些Polyfill程式才可以支援,請參考下圖所示:

clip_image004

圖 2:執行在伺服端的Blazor Server。

不管是上述哪一類的「Blazor」應用程式,都是以「元件 (Component) 」為基礎,「元件」 是一個附檔名為「.razor」檔案,其中可以使用Razor標籤來定義UI元素,也稱做「Razor Component」,例如頁面、對話方塊或資料輸入表單等等,並透過C#程式語法來設計UI渲染(UI rendering)邏輯,或處理事件。當編譯應用程式時,「元件」將會編譯成 一個.NET 類別。

 

使用Visual Studio 2019開發工具建立Blazor Server App

首先利用Visual Studio 2019開發工具來建立一個新專案,啟動 Visual Studio 2019之後,可以看到以下畫面,點選「Create a new Project」項目,然後按「Next」按鈕進入下一個畫面,請參考下圖所示:

clip_image006

圖 3:啟動 Visual Studio 2019。

在「Create a New Project」對話盒中,選取「Blazor App」項目,然後按「Next」按鈕進入下一個畫面,請參考下圖所示:

clip_image008

圖 4:選取「Blazor App」樣版專案。

在下一個步驟可以讓你設定專案名稱,如「BlazorApp1」,以及專案存放路徑後按下「Create」按鈕,請參考下圖所示:

clip_image010

圖 5:設定專案。

下一個畫面將可設定是否使用驗證功能,或啟用HTTPS或加裝Docker功能來建立專案,請參考下圖所示,目前直接使用預設值,按下「Create」按鈕之後將開始建立專案:

clip_image012

圖 6:建立專案。

新建立的範本網站結構如下圖所示,「wwwroot」資料夾存放網站靜態資料,例如圖示檔、樣式表。「Data」資料夾存放模型定義以及存取資料的服務程式;「Pages」資料夾用來存放元件(Component)程式碼;「Shared」資料夾用來存放共用的元件程式碼;「App.razor」檔案用來設定路由;「appsettings.json」用來進行組態設定;「Program.cs」檔案中包含一個「Main」方法,定義程式進入點:

clip_image014

圖 7:「Blazor App」範本網站檔案結構。

基本上第一個Blazor Server App便已經建立完成,「Pages」資料夾下的「Index.razor」是一個元件,定義網站首頁要顯示的內容。在Visual Studio開發工具,按CTRL+F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同),Visual Studio開發工具便會自動啟動一個開發階段用的網站伺服器IIS Express,接著會啟動瀏覽器,可看到首頁如下圖所示:

clip_image016

圖 8:網站首頁執行結果。

我們回頭過來看一下程式,檢視專案根目錄隙的「Startup.cs」檔案,其中「Startup」類別的「ConfigureServices」方法,叫用了「AddServerSideBlazor」方法在專案中加入了「Blazor」的服務。「Configure」方法中則設定的請求處理管理(Request Pipeline),並叫用「UseEndpoints」方法設定「SignalR」端點。

  • Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using BlazorApp1.Data;

namespace BlazorApp1 {
  public class Startup {
    public Startup( IConfiguration configuration ) {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices( IServiceCollection services ) {
      services.AddRazorPages();
      services.AddServerSideBlazor();
      services.AddSingleton<WeatherForecastService>();
    }

    public void Configure( IApplicationBuilder app , IWebHostEnvironment env ) {
      if ( env.IsDevelopment() ) {
        app.UseDeveloperExceptionPage();
      }
      else {
        app.UseExceptionHandler( "/Error" );
        app.UseHsts();
      }

      app.UseHttpsRedirection();
      app.UseStaticFiles();

      app.UseRouting();

      app.UseEndpoints( endpoints => {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage( "/_Host" );
      } );
    }
  }
}

Blazor Server整合了ASP.NET Core Endpoint Routing功能,你可以看到「UseEndpoints」方法中叫用了「MapBlazorHub」方法,以接受連線。若找不到請求的路由,就會執行「_Host.cshtml」,從程式中得知,預設將會渲染「App」元件(App.razor)。

按照慣例裝載程式的檔案名稱為「_Host.cshtml」,參考以下列表,其中的 <app> 標籤表示它將裝載一個Blazor App。而「RenderComponentAsync」方法,是用來啟用伺服端預渲染(prerender)功能,可在用戶端尚未建立連線時,預先在伺服端進行渲染的動作。檔案下方則引用了「blazor.server.js」,其中的JavaScript程式碼用於建立用戶端連線。將「RenderMode」設定為「ServerPrerendered」表示元件渲染的結果是靜態的HTML。

  • _Host.cshtml

@page "/"
@namespace BlazorApp1.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorApp1</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
    </app>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

「App.razor」檔案包含了啟用路由元件(Router component)的程式碼,使用<Router>標籤來定義路由。「RouteView」元件的「DefaultLayout」設定預設的版面頁為「Shared」資料夾中的「MainLayout」元件,「RouteView」元件是一個定位點,若有找到相符的路由,便將對應的自訂元件渲染完的結果套用版面頁呈現在此。若找不到相符的路由,則會顯示「<NotFound>」樣版定義的內容,透過「LayoutView」套用指定的版面頁顯示自訂錯誤訊息:「<p>Sorry, there's nothing at this address.</p>」。

  • App.razor

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

 

「MainLayout.razor」檔案定義網頁版面配置(layout),需繼承「LayoutComponentBase」類別,此類別定義了「Body」屬性,程式中使用「@Body」將路由相符的自訂元件渲染的結果插入這個位置。

  • MainLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>


如果想要蓋過預設版面配置頁的設定,你可以在「_Imports.razor」設定版面配置頁,這個檔案也可以加入using的語法,引用元件程式碼所需的命名空間,以下程式列表是專案根目錄下的「_Imports.razor」檔案:

  • _Imports.razor

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorApp1
@using BlazorApp1.Shared

專案內每一個資料夾中都可以選擇性的包含一個「_Imports.razor」檔案,若要蓋掉預設版面配置頁,我們可以這樣做,例如在「Pages」資料夾中加入一個「_Imports.razor」檔案,設定版面配置頁,程式參考如下:

  • Pages/_Imports.razor

@layout BlazorApp1.Shared.MyLayout

然後在「Shared」資料夾中,加入一個「MyLayout」檔案,程式參考如下:

  • Shared /MyLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <h1>
        my layout
    </h1>
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>


這樣「Pages」資料夾中的元件就會自動套用「MyLayout.razor」檔案做版面配置,執行結果請參考下圖所示:

clip_image018

圖 9:套用版面配置頁。

「Pages」資料夾下的「Index.razor」檔案實作了Blazor元件(Blazor Component),首頁的內容只包含靜態HTML標籤如下列表:

  • Index.razor

@page "/"

<h1> Hello, world! </h1>

Welcome to your new app.

 

第一行程式碼使用「@page」指示詞定義了路由。因此只要執行網站首頁,「Index.razor」元件會便執行渲染(Rendering)動作在記憶體中建立渲染樹(render tree),用來更新DOM。

「Pages」資料夾下的「Counter.razor」與「FetchData.razor」元件除了包含HTML標籤之外,還包含了使用C#語言撰寫的程式邏輯。

若在檢視「Index.razor」在瀏覽器中執行的結果,可以看到瀏覽器接收到以下標籤與程式碼,透過JavaScript(blazor.server.js)來運行:

  • Index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorApp1</title>
    <base href="/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        <!--Blazor:{"sequence":0,"type":"server","prerenderId":"fbdab358cbae4687b63d36820179ef00",

"descriptor":"CfDJ8NM4coV2aRBOmlH3B1B3ovM-qwAItkU-H7OtMCPj6GZOCtGF-5FvWJqaFA5S7S6VBkI3TRz5IzbtEOPSig4Kn4_ILMucUNZwv04DNOCo56nngdV38AyoNDZZehcj6EEsKfsOKZZhb6ID-rwTP_VHMUGxzdkqGczhLkddXi7pb33HhfgXBNjqIWHkGMDY0yrHqSpon1MjHBi7pPeA4kEIYJolLIj4uu0-e7WGCwVPH9JF9xLVOpxWrlUjFdYU6bpG6z1Vx6r8hhOHDuw-9suLIwC4lUf0NTmyCxKLkFcmhjka"}-->
        <div class="sidebar">
            <div class="top-row pl-4 navbar navbar-dark">
                <a class="navbar-brand" href>BlazorApp1</a>
                <button class="navbar-toggler">
                    <span class="navbar-toggler-icon"></span>
                </button>
            </div>

            <div class="collapse">
                <ul class="nav flex-column">
                    <li class="nav-item px-3">
                        <a href="" class="nav-link active">
                            <span class="oi oi-home" aria-hidden="true"></span> Home
                        </a>
                    </li>
                    <li class="nav-item px-3">
                        <a href="counter" class="nav-link">
                            <span class="oi oi-plus" aria-hidden="true"></span> Counter
                        </a>
                    </li>
                    <li class="nav-item px-3">
                        <a href="fetchdata" class="nav-link">
                            <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
                        </a>
                    </li>
                </ul>
            </div>
        </div>

        <div class="main">
            <div class="top-row px-4">
                <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
            </div>

            <div class="content px-4">
                <h1>Hello, world!</h1>

                Welcome to your new app.

            </div>
        </div>
        <!--Blazor:{"prerenderId":"fbdab358cbae4687b63d36820179ef00"}-->
    </app>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

 

關於版面配置頁的設定,我們再做一下說明,若是只有個別的元件要套用版面配置頁,則可以直接在元件的程式使用「@layout」設定,例如:

  • Index.razor

@layout BlazorApp1.Shared.MyLayout

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

建立Hello元件

「Blazor」中的元件(Component)也稱為「Razor」元件(Razor Component),一個「*.razor」檔案定義一個「Blazor」元件。一個「Blazor」元件是一個.NET類別,定義一個可以在網頁中重複使用的Web使用者介面(Web UI)。

讓我們開始來試寫一個自訂的「Hello」元件,首先在專案中「Pages」資料夾加入一個「Razor Component」範本,從「Solution Explorer」視窗 -「Pages」資料夾上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「New Item」項目,請參考下圖所示:

clip_image020

圖 10:建立新項目。

從「Add New Item」對話盒中,選取「Visual C#」-「ASP.NET Core」分類下「Razor Component」項目,然後在下方將「Name」設定為「Hello.razor」最後按下「Add」按鈕,請參考下圖所示:

clip_image022

圖 11:在專案中加入「Razor Component」項目。

特別注意,元件的名稱必需以大寫的英文字開始,不可以使用小寫,例如「Hello.razor」是有效的名稱,而「hello.razor」則是無效的名稱。接著在「Hello.razor」檔案中加入以下程式碼:

  • Hello.razor

@page "/hello"
<h1> Hello </h1>
<p>
    Name :
    <input placeholder="Enter Your Name " @bind="myName" />
</p>
<br />
<p>
    Message : @msg
</p>
<button class="btn btn-primary" @onclick="SayHello"> Click me </button>
@code {
    private string myName;
    private string msg;
    private void SayHello() {
        msg = $"Hello {myName}";
        myName = string.Empty;
    }
}

「Hello.razor」檔案第一行以「@page」指示詞開始。其後的字串定義了路由。也就是說「Hello」元件會負則處理瀏覽器送過來的「/hello」請求。元件可以不需要「@page」指示詞來處理路由,這樣的元件可以插入別的元件之中使用。

「Hello」元件使用標準的HTML標籤定義UI介面,程式處理邏輯則是使用Razor語法(使用C#語言)。HTML標籤與程式邏輯將會在編譯階段轉換成一個元件類別(Component Class),「Hello.razor」檔案的名稱就被拿來當做類別的名稱(不含附檔名)。以此例而言「Hello」元件的完整類別名稱為「BlazorApp1.Pages.Hello」,此類別將會自動繼承自「Microsoft.AspNetCore.Components.ComponentBase」類別。

「@code」區塊中定義了「Hello」類別的成員與元件的邏輯,其中「myName」與「msg」將編譯成「private」欄位(Field),「SayHello」則變成方法,當然你也可以在其中撰寫事件處理程式碼。

「@屬性名稱」或「@欄位名稱」語法可以用來設定資料繫結,例如<input>欄位之中透過「@bind="myName"」attribute繫結到「myName」欄位:

<input placeholder="Enter Your Name " @bind="myName" />

事件註冊的語法有點類似JavaScript,使用HTML attribute,例如以下範例程式碼註冊按鈕的「Click」事件觸發後,將會叫用「Hello」元件的「SayHello」方法:

<button class="btn btn-primary" @onclick="SayHello"> Click me </button>

 

元件測試

選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。在Visual Studio開發工具,按CTRL+F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同,請修改為實際的埠號),然後在瀏覽器輸入以下URL:

https://localhost:44369/hello

這個範例程式的執行結果參考如下圖所示,網頁中將會包含一個文字方塊,與一個按鈕:

clip_image024

圖 12:「Hello」元件。

只要在文字方塊中輸入名字,再按下按鈕就可以在下方看到歡迎訊息,請參考下圖所示:

clip_image026

圖 13:「Hello」元件執行結果。

當按下「Click me」按鈕,便叫用「SayHello」方法,「Hello」元件會重新產生渲染樹(render tree),接著拿新產生的渲染樹與舊的渲染樹做比對,將兩者之間的差異套用到DOM,接著畫面中就會顯示「Hello mary」歡迎的訊息。

 

使用元件

若有一個「ServerTime.razor」元件程式如下列表:

  • Shared \ServerTime.razor

<p>
    Server Time is : @t
</p>

@code {
    string t = DateTime.Now.ToLongTimeString();
}


「ServerTime」元件不需要路由,而是提供功能讓其它元件來重複叫用,因此不需要在檔案上方加上「@page」指示詞來定義路由。同時,為了讓網站所有元件都可以使用到它,我們將「ServerTime.razor」檔案放在網站中「Shared」資料夾下。

接著修改「Hello.razor」檔案,加入「< ServerTime>」標籤,便可以在「Hello」組件中使用「ServerTime」元件:

  • Hello.razor

@page "/hello"
<h1> Hello </h1>
<p>
    Name :
    <input placeholder="Enter Your Name " @bind="myName" />
</p>
<br />
<p>
    Message : @msg
</p>

<p>
    <ServerTime />
</p>

<button class="btn btn-primary" @onclick="SayHello"> Click me </button>
@code {
    private string myName;
    private string msg;
    private void SayHello() {
        msg = $"Hello {myName}";
        myName = string.Empty;
    }
}

選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。

在Visual Studio開發工具,按CTRL+F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同,請修改為實際的埠號),然後在瀏覽器輸入以下URL:

https://localhost:44369/hello

這個範例程式的執行結果參考如下圖所示:

clip_image028

圖 14:重複使用元件。

使用參數

元件可以設計參數,如此便可以在父元件傳遞資料到子元件。只要在子元件將參數定義成「public」的屬性,並在屬性前方套用「Parameter」Attribute,例如修改「ServerTime.razor」檔案,加入一個「public」的「format」屬性,用於設定時間顯示格式:

  • ServerTime.razor

<p>
    Server Time is : @GetTime()
</p>

@code {

    [Parameter]
    public string format { get; set; }

    private string GetTime() {

        return DateTime.Now.ToString( format );
    }

}

修改「Hello.razor」檔案,使用「ServerTime」元件時,利用HTML attribute「format="tt hh:mm:ss"」設定參數:

  • Hello.razor

@page "/hello"
<h1> Hello </h1>
<p>
    Name :
    <input placeholder="Enter Your Name " @bind="myName" />
</p>
<br />
<p>
    Message : @msg
</p>

<p>
    <ServerTime format="tt hh:mm:ss" />
</p>

<button class="btn btn-primary" @onclick="SayHello"> Click me </button>
@code {
    private string myName;
    private string msg;
    private void SayHello() {
        msg = $"Hello {myName}";
        myName = string.Empty;
    }
}


 

當瀏覽器未關閉的情況下,當你修改了Blazor App的程式碼,用戶端會自動跳出提示,是否重新連接到伺服器執行新程式,請參考下圖所示,這對開發來說省了很多功夫。

clip_image030

圖 15:程式自動重載執行。

這個範例程式的執行結果參考如下圖所示:

clip_image032

圖 16:使用參數。

使用導覽功能

「<NavLink>元件會產生HTML <a>標籤,建立超連結。範本網站的導覽功能定義在「NavMenu.razor」檔案之中,而「<NavLink>元件套用的css類別則是來自於「Bootstrap」套件:

  • 「NavMenu.razor」檔案

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp1</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="hello">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Hello
            </NavLink>
        </li>
    </ul>
</div>

@code {
    bool collapseNavMenu = true;

    string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

「<NavLink>元件的「Match」attribute設定為「NavLinkMatch.All」表示請求的URL要完全相符「<NavLink>才會有作用(Active),修改完成之後,網站首頁看起來如下:

clip_image034

圖 17:使用導覽功能。

Tags:

.NET Magazine國際中文電子雜誌 | 許薰尹Vivid Hsu | Blazor

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List