2024年7月18日 星期四

.NET 8 Blazor 001 - 了解 SSR Static Server Render 運作模式

.NET 8 Blazor 001 - 了解 SSR Static Server Render 運作模式

在 2023 年底,微軟正式推出 .NET 8 平台,對於 Blazor 這個 UI 開發框架,有著許多重大變化,其中最為重要的就是關於 Render 渲染(或稱為 轉譯)模式的運作機制,在微軟官方的文件中,將會有這樣的定義 :

轉譯一詞意指產生瀏覽器所顯示的 HTML 標記。

在 Blazor 中,轉譯是指將組件樹狀結構轉換為 HTML 標記。在 .NET 8 之前的版本,將會提供這兩種轉譯模式:伺服器端轉譯和客戶端轉譯。用戶端轉譯 (CSR) 意指最終的 HTML 標記由用戶端上的 Blazor WebAssembly 執行階段產生。伺服器端轉譯 (SSR) 意指最終的 HTML 標記由伺服器上的 ASP.NET Core 執行階段所產生。

在 .NET 8 中,Blazor 支援了一種新的運作模式,這個新的運作模式稱為 SSR (Static Server Render) 靜態伺服器渲染模式,這個模式將會讓 Blazor 應用程式在伺服器端進行渲染,然後再將渲染結果傳送給客戶端,這樣可以讓 Blazor 應用程式在客戶端的效能得到提升。

這裡總結 .NET 8 Blazor 支援的轉譯

  • 靜態伺服器

    靜態伺服器端轉譯 Static Server-Side Render (SSR)

    轉譯位置:伺服器

  • 互動式伺服器

    使用 Blazor Server 的互動式伺服器端轉譯 Interactive server-side rendering (interactive SSR) 。

    轉譯位置:伺服器

  • 互動式 WebAssembly

    使用 Blazor WebAssembly 的用戶端轉譯 Interactive WebAssembly , Client-side rendering (CSR)。

    轉譯位置:Client

  • 互動式自動 Interactive Auto

    一開始使用 Blazor Server 進行互動式 SSR,然後在下載 Blazor 套件組合之後,於後續造訪時使用 CSR。

    轉譯位置:伺服器,然後用戶端

要如何充分應用與善用這樣的架構,將會是未來 Blazor 開發者需要思考的議題。因此,了解到各種轉譯模式的運作原理,將會有助於未來 Blazor 開發者在開發 Blazor 應用程式時,能夠更加靈活的運用這些技術。

在這篇文章中,將會介紹如何在 .NET 8 Blazor 應用程式中,使用 SSR 靜態伺服器渲染模式,這是一種採用讓 Blazor 應用程式在伺服器端進行渲染,然後再將渲染結果傳送給客戶端。

這一系列的文章,將會採用 Visual Studio 17.10.1 IDE 作為開發工具,並且使用 .NET 8 作為開發平台。

建立測試專案

請依照底下的操作,建立起這篇文章需要用到的練習專案

  • 打開 Visual Studio 2022 IDE 應用程式
  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗右半部
    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [Web]
  • 在中間的專案範本清單中,找到並且點選 [Blazor Web App] 專案範本選項

    A project template for creating a Blazor Web app that support both server-side rending and client interactivity. This template can be used for web apps with rich dynamic user interfaces (UIs)

  • 點選右下角的 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗
  • 找到 [專案名稱] 欄位,輸入 csBlazorSSR 作為專案名稱
  • 在剛剛輸入的 [專案名稱] 欄位下方,確認沒有勾選 [將解決方案與專案至於相同目錄中] 這個檢查盒控制項

  • 點選右下角的 [下一步] 按鈕
  • 現在將會看到 [其他資訊] 對話窗
  • 在 [架構] 欄位中,請選擇最新的開發框架,這裡選擇的 [架構] 是 : .NET 8.0 (長期支援)
  • 在 [驗證類型] 欄位中,請選擇 [無]
  • 勾選 [針對 HTTPS 進行設定] 檢查盒欄位
  • 在 [Interactive render mode] 欄位中,請選擇 [None]
  • 在 [Interactivity location] 欄位中,請選擇 [Per pages/component]
  • 勾選 [Include sample pages] 檢查盒欄位
  • 勾選 [Do not use top-level statements] 檢查盒欄位

    這裡的這個操作,可以由讀者自行決定是否要勾選這個檢查盒控制項

  • 不要勾選 [在 .NET Aspire 協調流程中登入] 檢查盒欄位

  • 請點選右下角的 [建立] 按鈕

稍微等候一下,這個 Blazor Web App 專案將會建立完成

Program.cs - Blazor 專案程式進入點

現在依序從程式進入點的 Program.cs 檔案中,了解到關於 Blazor SSR 的設定方式

在專案的根目錄下,找到 Program.cs 檔案,並且打開這個檔案,這個檔案的內容如下

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>();

app.Run();

對於 WebApplication.CreateBuilder 這個方法,將會回傳一個 WebApplicationBuilder 物件,這個物件將會用來設定應用程式的服務和中介軟體 (管線和路由)。

在上述程式碼中,使用了 builder.Services.AddRazorComponents(); 這個方法,這個方法將會將 Razor 元件 ( 副檔案名稱為 .razor ) 服務加入到容器中,這個服務將會用來支援 Razor 元件的渲染。

這個 builder.Build() 方法將會 Build 出一個 WebApplication 物件,這個物件將會用來設定應用程式的 HTTP 請求管線。

在最後第二行程式碼中,使用了 app.MapRazorComponents<App>(); 這個方法,這個方法將會將 App 元件 (App.razor) 指定為預設根元件 (載入的第一個元件)。

最後的 app.Run(); 方法將會啟動這個 Blazor 應用程式。此時,這個 Web 專案將會啟動起來。

App.razor - Blazor 應用程式的根元件

從 [Components] 資料夾內找到並且打開 [App.razor] 這個檔案,就會看到底下內容

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.css" />
    <link rel="stylesheet" href="csBlazorSSR.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

從這個 [App.razor] 檔案中可以看出,這是一個相當簡單的 HTML5 的頁面,這個頁面中包含了一個 <head> 標籤和一個 <body> 標籤,這個頁面中還包含了一個 <Routes /> 元件,這個元件將會用來顯示路由的內容。而在 <head> 標籤中,還包含了一個 <HeadOutlet /> 元件,這個元件將會用來轉譯 PageTitle 和 HeadContent 元件提供的內容。

若執行這個專案之後,首先將會路由到 [Home] 頁面,現在可以查看該網頁的原始碼,可以得到底下的 <head> 區段的內容。

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <base href="/">
  <link rel="stylesheet" href="bootstrap/bootstrap.min.css">
  <link rel="stylesheet" href="app.css">
  <link rel="stylesheet" href="csBlazorSSR.styles.css">
  <link rel="icon" type="image/png" href="favicon.png">
  <title>Home</title>
</head>

在這個操作例子中,可以看到 <HeadOutlet /> 元件,將會被替換成為 <title>Home</title>。而網頁的標題將會被設定為 [Home],如下圖

現在切換到 [Weather] 頁面,可以看到網頁的標題將會被設定為 [Weather Forecast],此時查看該頁面原始碼,就會看到原先的 <title>Home</title> 變成了 <title>Weather</title>,如下圖

這樣的設計方式,將會讓 Blazor 應用程式的開發者可以更加靈活的設定網頁的標題,這樣的設計方式將會讓 Blazor 應用程式的開發者可以更加靈活的設定網頁的標題。

而對於 <Routes /> ,這是一個 Blazor 的 Razor 元件,這個元件將會用來顯示路由的內容,這個元件將會根據瀏覽器的 URL 來決定要顯示的 Razor 元件。

Routes.razor - Blazor 路由元件

在 [Components] 資料夾內找到並且打開 [Routes.razor] 這個檔案,就會看到底下內容

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

<Router AppAssembly="typeof(Program).Assembly"> : Router 組件是 Blazor 中用來處理路由的組件。AppAssembly 屬性指定了應用程式的主要程式集,Blazor 會在這個程式集中尋找路由定義。typeof(Program).Assembly 表示應用程式的主程式集,即通常包含路由定義的程式集。

<Found Context="routeData"> : Found 是 Router 組件的子組件之一,用來處理找到匹配路由的情況。Context 屬性定義了一個變數物件(這裡是 routeData),這個變數會包含匹配路由的相關資料。

<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> : RouteView 是一個用來呈現匹配路由組件的組件。 RouteData:這個屬性將 Found 組件中的 routeData 傳遞給 RouteView,用來告訴 RouteView 應該呈現哪個組件。 DefaultLayout:指定一個預設的版面配置。這裡使用了 typeof(Layout.MainLayout),表示當匹配的組件沒有明確指定版面配置時,會使用 MainLayout 這個版面配置。

<FocusOnNavigate RouteData="routeData" Selector="h1" /> : FocusOnNavigate 是一個自定義的組件,用來在導航後設定焦點。 RouteData:同樣地,將 routeData 傳遞給 FocusOnNavigate,用來觸發導航事件。 Selector:指定一個 CSS 選擇器,FocusOnNavigate 會在導航後將焦點設定到符合這個選擇器的元素上。這裡的 h1 表示導航後會將焦點設定到第一個 <h1> 元素上。

這段程式碼定義了 Blazor 應用程式的路由邏輯。當使用者導航到一個 URL 時,Router 組件會在指定的程式集中尋找匹配的路由。如果找到匹配的路由,Found 組件會將 routeData 傳遞給 RouteView 和 FocusOnNavigateRouteView 負責呈現匹配的組件,並且應用指定的版面配置(如果匹配的組件沒有指定版面配置,則使用預設的 MainLayout)。FocusOnNavigate 則負責在導航後將焦點設定到指定的元素上,這裡是將焦點設定到第一個 <h1> 元素。

觀察 SSR 靜態伺服器渲染模式

現在再次執行這個專案,接著,開啟無痕視窗

按下 F12 按鍵,進入到開發人員模式,切換到 [Network] 標籤頁次,接著在網址列輸入 https://localhost:7193/,就會看到底下的畫面

然後按下 Enter 鍵之後,觀察 [Network] 標籤頁次,可以看到底下的畫面

在 [Network] 標籤頁次中,觀察到第一個請求是 https://localhost:7193/,這個請求是用來下載首頁的 HTML 內容,這與傳統的 ASP.NET Core MVC 或者 ASP.NET Core Razor Page 一樣,所有的 HTML 內容都是在後端伺服器中產生出來,透過 HTTP 通訊協定 Response 回應到瀏覽器用戶端上,接著,瀏覽器會將這些 HTML 渲染到瀏覽器畫面上。

現在,在瀏覽器畫面上,點選 [Weather] 頁面,觀察 [Network] 標籤頁次,可以看到底下的畫面

此時將會看到送出一個 GET https://localhost:7193/weather URL 請求,這個請求是用來下載 Weather 頁面的 HTML 內容,不過,這次所送出的類型並不是 [document] 而是 [fetch] ,這是 .NET 8 Blazor SSR 推出的新功能,這裡僅會取得 Weather 更新後的 HTML 內容,將這些內容渲染到瀏覽器網頁上,因此,無須重新全部載入所有網頁、css、javascrit 檔案,可以提升運行速度。 




2024年4月9日 星期二

Elasticsearch 系列 : 建立與使用 Docker 環境下的 Elasticsearch 與 Kibana

Elasticsearch 系列 : 建立與使用 Docker 環境下的 Elasticsearch 與 Kibana

由於最近半年都在從事於 Elasticsearc 方面的專案開發,因為工作上的需要,因此將會把最近用於 Elasticsearc 相關開發技術與方法記錄下來,方便日後查詢,也讓更多人可以認識這個相當優異的工具,以及如何透過 C# 最到相關的應用。

底下為 2023 所撰寫的關於 Elasticsearch 的文章清單

由於之前所寫的文章,都是採用安裝在一台後端伺服器上的 Elasticsearch 服務來進行開發,在這篇文章將會介紹如何透過 Docker 來建立 Elasticsearch 與 Kibana 的環境,讓開發者可以在本機端進行開發,並且可以透過 Kibana 來進行資料庫的管理。

建立 Elasticsearch 要用到的網路

在建立 Elasticsearch 與 Kibana 的 Docker 環境之前,我們需要先建立一個網路,讓 Elasticsearch 與 Kibana 這兩個容器可以透過這個網路來進行溝通。

請在命令提示字元中輸入以下指令

docker network create elastic

執行結果如下

建立 Elasticsearch 要用到的網路

使用底下指令來確認是否有建立成功

docker network ls

確認是否有建立成功

建立與啟動 Elasticsearch Container

接著我們需要建立 Elasticsearch 的 Docker Image,請在命令提示字元中輸入以下指令

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.2

執行結果如下:

建立與啟動 Elasticsearch Image

接著我們需要建立 Elasticsearch 的 Docker Container,請在命令提示字元中輸入以下指令

docker run -it --name elasticsearch --net elastic -p 9200:9200 -e "discovery.type=single-node" -e ELASTIC_PASSWORD=elastic docker.elastic.co/elasticsearch/elasticsearch:8.13.1

上述的指令中,使用了 -e ELASTIC_PASSWORD=elastic 來設定 Elasticsearch 的密碼,這個密碼是用來登入 Elasticsearch 的,請記得保留這個密碼,因為之後會用到。另外,使用了 -e "discovery.type=single-node" 來設定 Elasticsearch 的節點類型為單節點,這樣就可以讓 Elasticsearch 在單節點的環境下運行。

執行結果如下:

建立與啟動 Elasticsearch Container

請在命令題是字元視窗內搜尋 [Enrollment token] 文字,就可以看到底下內容

ℹ️  Configure Kibana to use this cluster:
• Run Kibana and click the configuration link in the terminal when Kibana starts.
• Copy the following enrollment token and paste it into Kibana in your browser (valid for the next 30 minutes):
  eyJ2ZXIiOiI4LjEzLjEiLCJhZHIiOlsiMTcyLjE4LjAuMjo5MjAwIl0sImZnciI6ImUxYjUxZmZlYzkwMmZkYjcwNmM4Y2ZlYTgzMTZiOGUxZmU5NWFlNDAxNTU5MzczMTA1Y2U3Mzg3N2Q2Njc2MTciLCJrZXkiOiJ5aC1Fd0k0QlJDTTYtc3dueUJ3bTpTWlRJeEZBMFJXMmZ6eVBhUzlQOVJBIn0=

請將這段文字 eyJ2ZXIiOiI4LjEzLjEiLCJhZHIiOlsiMTcyLjE4LjAuMjo5MjAwIl0sImZnciI6ImUxYjUxZmZlYzkwMmZkYjcwNmM4Y2ZlYTgzMTZiOGUxZmU5NWFlNDAxNTU5MzczMTA1Y2U3Mzg3N2Q2Njc2MTciLCJrZXkiOiJ5aC1Fd0k0QlJDTTYtc3dueUJ3bTpTWlRJeEZBMFJXMmZ6eVBhUzlQOVJBIn0= 複製起來,因為之後會用到,這段文字稱之為 [Enrollment token]。

從執行結果中可以看到 Elasticsearch 已經成功的建立與啟動,並且可以看到底下內容:

接著我們可以透過瀏覽器來進行測試,請在瀏覽器中輸入以下網址

https://localhost:9200

執行結果如下:

首先,需要先輸入帳號與密碼,帳號為 elastic ,密碼為 elastic

測試 Elasticsearch 是否成功建立

完成後,點選登入按鈕,就會看到底下畫面

測試 Elasticsearch 是否成功建立

{
name: "eee625dddc63",
cluster_name: "docker-cluster",
cluster_uuid: "wKpWcpE8R6q9Ipu_odjzrQ",
version: {
number: "8.13.1",
build_flavor: "default",
build_type: "docker",
build_hash: "9287f29bba5e270bd51d557b8daccb7d118ba247",
build_date: "2024-03-29T10:05:29.787251984Z",
build_snapshot: false,
lucene_version: "9.10.0",
minimum_wire_compatibility_version: "7.17.0",
minimum_index_compatibility_version: "7.0.0"
},
tagline: "You Know, for Search"
}

建立與啟動 Kibana Container

使用底下指令啟動 Kibana 的 Docker Image

docker pull docker.elastic.co/kibana/kibana:8.13.1

執行結果如下:

建立與啟動 Kibana Image

接著我們需要建立 Kibana 的 Docker Container,請在命令提示字元中輸入以下指令

docker run -it --name kibana --net elastic -p 5601:5601 docker.elastic.co/kibana/kibana:8.13.1

執行結果如下:

建立與啟動 Kibana Container

從上圖的輸出文字的最後面,可以看到 Kibana has not been configured 的訊息,這表示 Kibana 還沒有設定完成,所以,接著我們可以透過瀏覽器來進行測試,請在瀏覽器中輸入以下網址

http://localhost:5601

執行結果如下:

測試 Kibana 是否成功建立

請在 [Enrollment token] 欄位中貼上剛剛複製下來的 [Enrollment token]

測試 Kibana 是否成功建立

請直接點選 [Configure Elastic] 按鈕,就會看到底下畫面

測試 Kibana 是否成功建立

在這個畫面中,可以看到需要提供驗證碼,所以,現在切換到 Kibana 的命令提示字元視窗,將會看如下圖

Kibana Verification required

最後一行的文字,將會是 Your verification code is: \u001b[30m\u001b[106m 106 918 \u001b[49m\u001b[39m,請將這段文字 106 918 複製起來,因為之後會用到,這段文字稱之為 [Verification code]。

將 [Verification code] 貼到瀏覽器上

Kibana Verification required

完成後,點選 [Verify] 按鈕,就會看到底下畫面

等候 kibana 完成初始化設定之後,就會看到底下畫面

現在可以正常連上 kibana

Kibana Verification required

請在 [Username] 欄位中輸入 elastic,在 [Password] 欄位中輸入 elastic,然後點選 [Log in] 按鈕,就可以看到底下畫面

第一次使用 Kibana

在這個畫面中,點選 [Explore on my own] 按鈕,就可以看到底下畫面

第一次使用 Kibana

其他說明

docker exec -it elasticsearch /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana

上述的指令表示了透過 elasticsearch-create-enrollment-token 來建立一個 enrollment token,這個 enrollment token 是用來讓 Kibana 與 Elasticsearch 之間進行溝通的,而 -s kibana 表示了這個 enrollment token 是用來給 Kibana 使用的。