主頁 > .NET開發 > 溫故知新,.Net Core遇見Blazor(FluentUI),屬于未來的SPA框架

溫故知新,.Net Core遇見Blazor(FluentUI),屬于未來的SPA框架

2021-05-05 13:28:40 .NET開發

什么是Blazor

Blazor是一個使用.NET生成互動式客戶端WebUI的框架:

  • 使用C#代替JavaScript來創建資訊豐富的互動式UI,
  • 共享使用.NET撰寫的服務器端和客戶端應用邏輯,
  • 將UI呈現為HTML和CSS,以支持眾多瀏覽器,其中包括移動瀏覽器,
  • 與新式托管平臺(如Docker)集成,

image

基于.Net Core提供客戶端Web開發的優勢

使用.NET進行客戶端Web開發可提供以下優勢:

  • 使用C#代替JavaScript來撰寫代碼,
  • 利用現有的.NET庫生態系統,
  • 在服務器和客戶端之間共享應用邏輯,
  • 受益于.NET的性能、可靠性和安全性,
  • 在Windows、Linux和macOS上使用VisualStudio保持高效作業,
  • 以一組穩定、功能豐富且易用的通用語言、框架和工具為基礎來進行生成,

Blazor組件

Blazor應用基于組件,Blazor中的組件是指UI元素,例如頁面、對話框或資料輸入表單,

組件是內置到.NET程式集的.NET C#類,它們用于:

  • 定義靈活的UI呈現邏輯,
  • 處理用戶事件,
  • 可以嵌套和重用,
  • 可作為Razor類別庫或NuGet包共享和分發,

組件類通常以Razor標記頁(檔案擴展名為.razor)的形式撰寫,Blazor中的組件有時被稱為Razor組件,Razor是一種語法,用于將HTML標記與專為提高開發人員作業效率而設計的C#代碼結合在一起,借助Razor,可使用Visual Studio中的IntelliSense編程支持在同一檔案中的HTML標記與C#之間切換,Razor Pages和MVC也使用Razor,與基于請求/回應模型生成的Razor PagesMVC不同,組件專門用于處理客戶端UI邏輯和構成,

Blazor使用UI構成的自然HTML標記,下面的Razor標記演示了一個組件(Dialog.razor),它顯示一個對話框,并處理在用戶選擇按鈕時發生的事件:

<div  style="width:22rem">
    <div >
        <h3 >@Title</h3>
        <p >@ChildContent</p>
        <button @onclick="OnYes">Yes!</button>
    </div>
</div>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

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

    private void OnYes()
    {
        Console.WriteLine("Write to the console in C#! 'Yes' button selected.");
    }
}

在上述示例中,OnYes是由按鈕的onclick事件觸發的C#方法,對話框的文本(ChildContent)和標題(Title)由在其UI中使用此組件的下述組件提供,

使用HTML標記將Dialog組件嵌入到另一個組件中,在以下示例中,Index組件(Pages/Index.razor)使用前面的Dialog組件,標記的Title屬性向Dialog組件的Title屬性傳遞標題的值,Dialog組件的文本(ChildContent)由<Dialog>元素的內容設定,向Index組件添加Dialog組件后,Visual Studio中的IntelliSense可加快使用語法和引數補全進行開發的速度,

@page "/"

<h1>Hello, world!</h1>

<p>
    Welcome to your new app.
</p>

<Dialog Title="Learn More">
    Do you want to <i>learn more</i> about Blazor?
</Dialog>

在瀏覽器中訪問父級Index組件時,將呈現該對話框,當用戶選擇此按鈕時,瀏覽器的開發人員工具控制臺會顯示由OnYes方法撰寫的訊息:

image

組件呈現為瀏覽器檔案物件模型(DOM)的記憶體中表現形式,它被稱為“呈現樹”,用于以靈活高效的方式更新UI,

檔案物件模型 (DOM) 是HTML和XML檔案的編程介面,它提供了對檔案的結構化的表述,并定義了一種方式可以使從程式中對該結構進行訪問,從而改變檔案的結構,樣式和內容,DOM 將檔案決議為一個由節點和物件(包含屬性和方法的物件)組成的結構集合,簡言之,它會將web頁面和腳本或程式語言連接起來,

一個web頁面是一個檔案,這個檔案可以在瀏覽器視窗或作為HTML原始碼顯示出來,但上述兩個情況中都是同一份檔案,檔案物件模型(DOM)提供了對同一份檔案的另一種表現,存盤和操作的方式, DOM是web頁面的完全的面向物件表述,它能夠使用如 JavaScript等腳本語言進行修改,

Blazor Web Assembly

Blazor Web Assembly單頁應用(SPA)框架,用于使用.NET生成互動式客戶端Web應用,Blazor Web Assembly使用無插件或將代碼重新編譯為其他語言的開放式Web標準,Blazor Web Assembly適用于所有新式Web瀏覽器,包括移動瀏覽器,

通過WebAssembly(縮寫為wasm),可在Web瀏覽器內運行.NET代碼,Web Assembly是針對快速下載和最大執行速度優化的壓縮位元組碼格式,Web Assembly是開放的Web標準,支持用于無插件的Web瀏覽器,

Web Assembly代碼可通過JavaScript(稱為JavaScript互操作性,通常簡稱為JavaScript互操作或JS互操作)訪問瀏覽器的完整功能,通過瀏覽器中的Web Assembly執行的.NET代碼在瀏覽器的JavaScript沙盒中運行,沙盒提供的保護可防御客戶端計算機上的惡意操作,

image

Blazor Web Assembly應用生成并在瀏覽器中運行時:

  • C#代碼檔案和Razor檔案將被編譯為.NET程式集,
  • 該程式集和.NET運行時將被下載到瀏覽器,
  • BlazorWebAssembly啟動.NET運行時,并配置運行時,以為應用加載程式集,BlazorWebAssembly運行時使用JavaScript互操作來處理DOM操作和瀏覽器API呼叫,

已發布應用的大小(其有效負載大小)是應用可用性的關鍵性能因素,大型應用需要相對較長的時間才能下載到瀏覽器,這會損害用戶體驗,Blazor Web Assembly優化有效負載大小,以縮短下載時間:

  • 在中間語言(IL)裁邊器發布應用時,會從應用洗掉未使用的代碼,
  • 壓縮HTTP回應,
  • .NET運行時和程式集快取在瀏覽器中,

Blazor托管模型

主要的Blazor托管模型在Web Assembly上的瀏覽器中運行客戶端,將Blazor應用、其依賴項以及.NET運行時下載到瀏覽器,應用將在瀏覽器執行緒中直接執行,UI更新和事件處理在同一行程中進行,應用資產作為靜態檔案部署到可為客戶端提供靜態內容的Web服務器或服務中,

image

如果創建了Blazor Web Assembly應用進行部署,但沒有后端ASP.NET Core應用來為其檔案提供服務,那么該應用被稱為獨立Blazor Web Assembly應用,如果創建了應用進行部署,但沒有后端應用來為其檔案提供服務,那么該應用被稱為托管的Blazor Web Assembly應用,托管的Blazor Web Assembly Client應用通常使用WebAPI呼叫或SignalR(結合使用ASP.NET Core SignalRBlazor)通過網路與后端Server應用互動,

blazor.webassembly.js腳本由框架和句柄提供:

  • 下載.NET運行時、應用和應用依賴項,
  • 初始化運行應用的運行時,

Blazor Web Assembly托管模型具有以下優點:

  • 沒有.NET服務器端依賴項,應用下載到客戶端后即可正常運行,
  • 可充分利用客戶端資源和功能,
  • 作業可從服務器轉移到客戶端,
  • 無需ASP.NETCoreWeb服務器即可托管應用,無服務器部署方案可行,例如通過內容分發網路(CDN)為應用提供服務的方案,

Blazor Web Assembly托管模型具有以下局限性:

  • 應用僅可使用瀏覽器功能,
  • 需要可用的客戶端硬體和軟體(例如WebAssembly支持),
  • 下載項大小較大,應用加載耗時較長,
  • .NET運行時和工具支持不夠完善,例如,.NETStandard支持和除錯方面存在限制,

在傳統Web應用和單頁應用(SPA)之間選擇

Atwood 定律:任何能夠用JavaScript撰寫的應用程式,最終必將用JavaScript撰寫,- Jeff Atwood

目前可通過兩種通用方法來構建Web應用程式:在服務器上執行大部分應用程式邏輯的傳統Web應用程式,以及在Web瀏覽器中執行大部分用戶界面邏輯的單頁應用程式(SPA),后者主要使用WebAPI與Web服務器通信,也可以將兩種方法混合使用,最簡單的方法是在更大型的傳統Web應用程式中托管一個或多個豐富SPA類子應用程式,

何時使用傳統Web應用程式:

  • 應用程式的客戶端要求簡單,甚至要求只讀,
  • 應用程式需在不支持JavaScript的瀏覽器中作業,
  • 團隊不熟悉JavaScriptTypeScript開發技術,

何時使用SPA:

  • 應用程式必須公開具有許多功能的豐富用戶界面,
  • 團隊熟悉JavaScriptTypeScriptBlazor Web Assembly開發,
  • 應用程式已為其他(內部或公共)客戶端公開API,

此外,SPA框架還需要更強的體系結構和安全專業知識,相較于傳統Web應用程式,SPA框架需要進行頻繁的更新和使用新框架,因此改動更大,相較于傳統Web應用,SPA應用程式在配置自動化生成和部署程序以及利用部署選項(如容器)方面的難度更大,

使用SPA方法改進用戶體驗時必須權衡這些注意事項,

傳統Web應用程式、SPA或Blazor應用之間進行選擇時要考慮的一些基本因素

因素 傳統 Web 應用 單頁面應用程式 Blazor 應用
需要團隊熟悉 JavaScript/TypeScript 最低 必需 最低
支持不帶腳本的瀏覽器 支持 不支持 支持
客戶端應用程式行為極少 適合 不必要 可行
豐富而復雜的用戶界面要求 受限 適合 適合

何時選擇傳統Web應用

應用程式的客戶端要求簡單,可能要求只讀

對許多Web應用程式而言,其大部分用戶的主要使用方式是只讀,只讀(或以讀取為主)應用程式往往比那些維護和操作大量狀態的應用程式簡單得多,例如,搜索引擎可能由一個帶有文本框的入口點和用于顯示搜索結果的第二頁組成,匿名用戶可以輕松提出請求,并且很少需要使用客戶端邏輯,同樣,一般而言,博客或內容管理系統中面向公眾的應用程式主要包含的內容與客戶端行為關系不大,此類應用程式容易構建為基于服務器的傳統Web應用程式,在Web服務器上執行邏輯,并呈現要在瀏覽器中顯示的HTML,事實上,網站的每個獨特頁面都有自己的URL,搜索引擎可以將其存為書簽和編入索引(默認設定,無需將此功能添加為應用程式的單獨功能),這也是此類情況的一個明顯優勢,

應用程式需在不支持JavaScript的瀏覽器中作業

如需在有限或不支持JavaScript的瀏覽器中作業的Web應用程式,則應使用傳統的Web應用作業流撰寫(或至少可以回退到此類行為),SPA需要客戶端JavaScript才能正常作業;如果沒有客戶端JavaScript,SPA不是好的選擇,

團隊不熟悉JavaScript或TypeScript開發技術

如果團隊不熟悉JavaScriptTypeScript,但熟悉服務器端Web應用程式開發,那相較于SPA,他們交付傳統Web應用的速度可能更快,除非以學習SPA編程為目的,或需要SPA提供用戶體驗,否則對已經熟悉構建傳統Web應用的團隊而言,選擇傳統Web應用的作業效率更高,

何時選擇SPA

應用程式必須公開具有許多功能的豐富用戶界面

SPA可支持豐富客戶端功能,當用戶執行操作或在應用的各區域間導航時無需重新加載頁面,SPA很少需要重新加載整個頁面,因此加載速度更快,可在后臺提取資料,并且對單個用戶操作的回應更快,SPA支持增量更新,可保存尚未完成的表單或檔案,而無需用戶單擊按鈕提交表單,SPA支持豐富的客戶端行為,例如拖放,比傳統應用程式更容易操作,可以將SPA設計為在斷開連接的模式下運行,對客戶端模型進行更新,并在重新建立連接后將更新最終同步回服務器,如果應用要求包括豐富的功能,且超出了典型HTML表單提供的功能,則選擇SPA樣式應用程式,

通常,SPA需要實作內置于傳統Web應用中的功能,例如在反映當前操作的地址欄中顯示有意義的URL(并允許用戶將此URL存為書簽或對其進行深層鏈接以便回傳此URL),SPA還應允許用戶使用瀏覽器的后退和前進按鈕尋找用戶意料之中的結果,

團隊熟悉JavaScript和/或TypeScript開發

撰寫SPA需要熟悉JavaScriptTypeScript以及客戶端編程技術和庫,團隊應有能力像使用Angular一樣使用SPA框架撰寫新式JavaScript

參考-SPA框架

  • Angular:https://angular.io
  • React:https://reactjs.org/
  • Vue.js:https://vuejs.org/

應用程式已為其他(內部或公共)客戶端公開API

如果已提供一個Web API供其他客戶端使用,則相較于在服務器端表單中復制邏輯,創建一個利用這些APISPA實作更加容易,用戶與應用程式互動時,SPA廣泛使用Web API來查詢和更新資料,

何時選擇Blazor

應用程式必須公開豐富用戶界面

與基于JavaScript的SPA一樣,Blazor應用程式可以支持豐富的客戶端行為,而無需多載頁面,這些應用程式對用戶的回應更快,僅獲取回應給定用戶互動所需的資料(或HTML),如果設計得當,可以將服務器端Blazor應用配置為以客戶端Blazor應用的形式運行,只需在此功能受支持后對它進行稍加更改即可,

與使用JavaScript或TypeScript開發相比,團隊更喜歡使用.NET開發

與使用JavaScriptTypeScript等客戶端語言相比,許多使用.NET和Razor的開發人員的作業效率更高,由于已經使用.NET開發了應用程式的服務器端,因此,使用Blazor可以確保團隊中的每名.NET開發人員都可以理解,并可能會生成應用程式前端的行為,

Blazor Server

Blazor將組件呈現邏輯從UI更新的應用方式中分離出來,Blazor ServerASP.NET Core應用中支持在服務器上托管Razor組件,可通過SignalR連接處理UI更新,

image

運行時停留在服務器上并處理:

  • 執行應用的C#代碼,
  • 將UI事件從瀏覽器發送到服務器,
  • 將UI更新應用于服務器發送回的已呈現的組件,

Blazor Server用于與瀏覽器通信的連接還用于處理JavaScript互操作呼叫,

image

JavaScript互操作

對于需要第三方JavaScript庫和訪問瀏覽器API的應用,組件與JavaScript進行互操作,組件能夠使用JavaScript能夠使用的任何庫或API,C#代碼可呼叫到JavaScript代碼,而JavaScript代碼可呼叫到C#代碼,

在ASP.NET Core Blazor中從.NET方法呼叫JavaScript函式

https://docs.microsoft.com/zh-cn/aspnet/core/blazor/call-javascript-from-dotnet?view=aspnetcore-5.0

從ASP.NET Core Blazor中的JavaScript函式呼叫.NET方法

https://docs.microsoft.com/zh-cn/aspnet/core/blazor/call-dotnet-from-javascript?view=aspnetcore-5.0

Blazor支持的平臺

瀏覽者 Version
Apple Safari,包括 iOS 當前?
Google Chrome,包括 Android 當前?
Microsoft Edge 當前?
Mozilla Firefox 當前?

?最新指的是瀏覽器的最新版本,

Blazor模板選項

  • Blazor WebAssembly專案模板:blazorwasm
  • Blazor Server專案模板:blazorserver

Blazor生命周期方法

  • OnInitializedAsyncOnInitialized方法,執行代碼來初始化組件,要執行異步操作,請在操作上使用OnInitializedAsyncawait關鍵字,

  • OnParametersSetAsyncOnParametersSet當組件已接收到的引數從其父和值被分配給屬性被呼叫,這些方法在組件初始化后以及每次呈現組件時執行,

  • OnAfterRenderAsyncOnAfterRender在組件完成渲染后呼叫,此時填充元素和組件參考,使用此階段使用呈現的內容執行其他初始化步驟,例如激活對呈現的DOM元素進行操作的第三方Java庫,

創建我的第一個Blazor應用

https://github.com/CraigTaylor/BlazorApp

1. 先準備".NET SDK (Software Development Kit)"環境,

如果之前沒有安裝的先安裝這個SDK:

dotnet-sdk-5.0.202-win-x64.exe

如果已經安裝過了,檢查下當前版本:

dotnet --version

image

2. 根據Blazor專案模板創建應用

1) 命令列方式創建

dotnet new blazorserver -o BlazorApp --no-https

這里通過DOTNET-CLI執行新建專案的命令,使用的是blazorserver這個專案模板,輸出專案檔案夾為BlazorApp,給該專案設定為不需要HTTPS模式--no-https

如果要創建Blazor WebAssembly專案,這里將blazorserver改成blazorwasm即可,

dotnet new blazorwasm -o BlazorApp --no-https

2) Visual Studio方式創建

打開Visual Studio 2019最新版本,創建新專案,找到Blazor相關的專案模板,

  • Blazor Server應用
  • Blazor WebAssembly應用

image

點擊下一步,輸入BlazorApp的專案名稱,點擊下一步進行創建,

image

目標框架,可以選.NET Core 3.1(長期支持),也可以選擇.NET 5.0(當前),去掉勾選復選框配置HTTPS,點擊創建即可,

image

創建成功之后,會自動打開專案解決方案,

image

3. 運行應用

1) 命令列方式創建

如果通過命令列方式創建,運行應用只需要執行:

dotnet watch run

通過DOTNET-CLIRun命令可以運行程式,這里添加watch引數的好處就是,等下如果改動了檔案,會熱多載,這樣除錯起來就很方便,

image

運行起來之后,會看到瀏覽器彈出應用首頁,

image

如果要退出運行,只需要執行如下命令即可:

Ctrl + C

image

2) Visual Studio方式創建

Visual Studio里面運行就很簡單了,直接點運行BlazorApp或者Ctrl + F5即可,

image

默認會打開一個終端控制臺界面,用來顯示Console日志,

image

運行起來之后,會看到瀏覽器彈出應用首頁,

4. 專案結構

  • Program.cs 啟用這個應用服務的入口節點,
  • Startup.cs 配置應用服務和中間件的地方,
  • App.razor 應用組件的根組件,也就是組件的入口位置,
  • BlazorApp/Pages 包含一些示例頁面,也是頁面目錄,
  • BlazorApp.csproj 描述應用專案及其依賴關系,

5. 認識首頁及組件

我們看到Pages/Index.razor這個主頁,

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

這里已經有了SurveyPrompt這樣一個組件,同時它有一個入參Title可以定制,

6. 查看子組件計數器

打開Pages/Counter.razor,這是一個計數器的組件,

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button  @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

我們可以從代碼看到,頂部@page "/counter"實際上這個頁面組件的目錄,當我們切換到它時,路徑也會變化,

image

每次點擊Click me按鈕的時候,就會從onclick事件去找到IncrementCount這個函式,在這個函式中,我們把currentCount做了加1,這時候,Current count的值會跟著重繪,可以看出只要系結好了資料,它就會幫我們自動去更新界面顯示了,還是很方便,

image

7. 在首頁添加子組件計數器

打開Pages/Index.razor,我們在末尾追加Counter即可,

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<Counter />

這時候,我們查看頁面就已經可以看到它了,

image

8. 修改組件添加屬性并使用

接下來,我們打開Pages/Counter.razor,我們給這個組件添加一個引數屬性IncrementAmount,默認值是1, 這樣我們可以從外部傳遞值進來修改它,并且我們讓currentCount不再是繼續加1了,而是改成加IncrementAmount的值,這樣我們就可以控制每次累加的幅度,

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button  @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    [Parameter]
    public int IncrementAmount { get; set; } = 1;

    private void IncrementCount()
    {
        currentCount += IncrementAmount;
    }
}

然后,我們回到Pages/Index.razor,在Counter組件上給他傳遞IncrementAmount的值進來,

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<Counter IncrementAmount="10" />

這時候,界面會自動多載,這時候,我們每次點擊Click me,這個計時器組件就會加10了,哈哈,是不是很簡單,

image

生成Blazor待辦事項串列應用

https://github.com/CraigTaylor/TodoList

1. 先準備".NET SDK (Software Development Kit)"環境,

如果之前沒有安裝的先安裝這個SDK:

dotnet-sdk-5.0.202-win-x64.exe

如果已經安裝過了,檢查下當前版本:

dotnet --version

image

2. 創建待辦事項串列Blazor應用

dotnet new blazorserver -o TodoList --no-https

通過DOTNET-CLI腳本的New命令,新建一個blazorserver專案模板,輸出目錄命名為TodoList,同時不需要HTTPS的支持,

image

3. 添加名為“Todo”的Blazor組件

cd TodoList

image

先切換到TodoList專案檔案夾中,

dotnet new razorcomponent -n Todo -o Pages

然后,通過DOTNET-CLI腳本的New命令,輸出目錄為Pages,新建名為Todorazorcomponent組件,

其中-n全稱為--name,用來指定創建的名稱,-o全稱為--output,用來指定在這個目錄進行創建,

Razor組件檔案名要求首字母大寫,打開Pages檔案夾,確認Todo組件檔案名以大寫字母T開頭,檔案名應為Todo.razor

image

將在專案檔案夾的Pages目錄中,新建得到一個Todo.razor檔案,

image

接下來,我們需要在Todo.razor檔案頂部添加針對該檔案組件的路由位置,

@page "/todo"

image

4. 將“Todo”的Blazor組件添加到左側導航

位于Shared檔案夾的NavMenu.razor組件是控制左側導航的,我們需要將“Todo”的Blazor組件添加到左側導航中,

NavMenu.razor組件中,找到ul元素下的li節點,參考前面的例子,新建屬于Todo組件的導航位置,

<div  @onclick="ToggleNavMenu">
    <ul >
        ...
        <li >
            <NavLink  href="https://www.cnblogs.com/taylorshi/p/todo">
                <span  aria-hidden="true"></span> ToDoList
            </NavLink>
        </li>
    </ul>
</div>

image

這里我們需要注意的幾個關鍵點是,NavLinkhref需要填寫組件的路由位置,其中span元素是選單圖示,緊跟著它的就是選單名稱,這里我們用ToDoList來作為選單名稱,

保存之后,我們通過執行run命令來運行,查看導航效果,

dotnet watch run

image

image

5. 添加“TodoItem”來存盤待辦事項資料,

這里一個小插曲就是為了更好的寫出TodoItem.cs這個模型類,我遇到了兩個不錯的VSC插件,這里就順帶記錄一下,

  • 名為C# Extensions的VSC插件,可以幫助我們支持在VSC中右鍵快速新建模型類和介面,有點類似VS中那種創建,至少幫你完成了當前命名空間的添加的活,所以值得推薦,

image

  • 名為C# XML Documentation Comments的VSC插件,平時我們在VS中寫代碼,習慣性的如果要添加函式或者屬性的注釋的話,上來就是///然后等待自動完成得到一串添加注釋的注釋體,這個插件就是來干這個事情的,效果還不錯,

image

這兩個插件,搜索C#這個關鍵詞就可以看到了,基本上排在前面,

有了它們,我們創建TodoItem模型類就如虎添翼了,

在專案的Data檔案夾中,我們右鍵,選擇New C# Class,輸入TodoItem,然后回車即可,

image

然后我們就自動完成,得到了一個基礎的空的TodoItem.cs模型類,它的namespace都是已經處理好了,我覺得比手寫強吧,

image

接下里,我們往TodoItem.cs模型類中添加兩個屬性(最好是走prop命令來新建),一個是Title來存盤待辦事項的標題,一個是IsDone來標記是否已完成該事項,

image

接下來,為了將來更好的閱讀這個模型,我們養成一個好習慣,給對應的模型和欄位都添加好注釋,這時候C# XML Documentation Comments這個VSC插件就要派上用場了,通過在所需要注釋代碼的開頭位置,輸入///然后回車即可,

namespace TodoList.Data
{
    /// <summary>
    /// 待辦事項
    /// </summary>
    public class TodoItem
    {
        /// <summary>
        /// 事項名稱
        /// </summary>
        /// <value></value>
        public string Title { get; set; }

        /// <summary>
        /// 事項是否已完成
        /// </summary>
        /// <value></value>
        public bool IsDone { get; set; }
    }
}

image

6. 實作并完善“Todo”組件的邏輯,

有了上一步的TodoItem.cs模型類,我們算是準備好了承載待辦事項業務的資料基礎,接下來我們就可以基于它來實作并完善我們的Todo組件的核心邏輯了,

首先,我們新建一個TodoList來承載所有的待辦事項,從業務上它應該是個TodoItem待辦事項的陣列,在Pages目錄的Todo.razor檔案的@code中,我們可以添加C#代碼,我們添加一個TodoList,注意記得添加using使用,

@code {

    /// <summary>
    /// 待辦事項串列
    /// </summary>
    /// <value></value>
    public List<TodoItem> TodoList { get; set; } = new List<TodoItem>();
}

image

然后,有了資料源TodoList,我們構建一個界面來承載這個資料源,這里最方便的就是ul-li組合了,在最外層ul中,我們針對TodoList做一個For-Each回圈遍歷,然后為每一項新建一個li來承載它,并且系結每一項的Title,這樣我們就可以看到這個元素了,

<ul>
    @foreach (var item in TodoList)
    {
        <li>@item.Title</li>
    }
</ul>

image

7. 添加對新增待辦事項的支持,

有了前面的TodoList資料源和ul-li展示層,接下來我們來添加一些修改資料的支持了,首先我們要支持的就是往TodoList里面插入新資料,

為了插入新資料,那么我們需要在界面上放出一個輸入框和一個提交按鈕吧,所以接下來我們在ul-li的后面,添加下這個新增項組合,

<input placeholder="輸入新待辦事項標題" @bind="AddTitle" />
<button @onclick="AddToDoItem">新建</button>

image

我們用一個input元素來讓用戶輸入新的待辦事項,placeholder的內容是提示語:輸入新待辦事項標題,它的值我們通過@bind來做,我們系結AddTitle這個變數的值,

并且,我們通過添加一個名為新建button元素來觸發@onclick事件,這里讓@onclick事件系結AddToDoItem方法,這里留意,等添加完新項之后,我們清空下AddTitle的值,以便下次添加新項,

為了接收上訴輸入資料和點擊事件的系結,我們也需要在@code中添加對應的支持,下面我們就添加對應的支持:

@code {

    ...

    /// <summary>
    /// 新增待辦項標題
    /// </summary>
    /// <value></value>
    public string AddTitle { get; set; }

    /// <summary>
    /// 添加待辦事項
    /// </summary>
    public void AddToDoItem()
    {
        if(!string.IsNullOrEmpty(AddTitle))
        {
            var todoItem = new TodoItem {
                Title = AddTitle
            };
            TodoList.Add(todoItem);
            AddTitle = string.Empty;
        }
    }
}

image

保存之后,會自動重繪界面,我們測驗下,是否符合我們的預期,

image

測驗后,我們看到,可以成功添加新項了,

image

8. 添加對修改待辦事項完成狀態的支持,

前面我們已經支持了展示待辦事項和添加待辦事項,那么最后的一環就是可以修改單個待辦事項的完成狀態了,也就是我們還沒使用到TodoItemIsDone屬性,

我們為每一個TodoItem項添加一個前置的復選框,以便于可以通過勾選的互動方式來改變它的完成狀態,

<h3>Todo (@TodoList.Count(x=>!x.IsDone))</h3>

<ul>
    @foreach (var item in TodoList)
    {
        <li>
            <input type="checkbox" @bind="item.IsDone" />
            <span>@item.Title</span>
        </li>
    }
</ul>

我們在li中添加一個input元素,type設定成checkbox,我們用它來驅動待辦事項的完成狀態,所以我們需要給它系結item.IsDone,不僅每一項的IsDone

同時,為了清晰看到我們是否真的改變了資料,我們在h3這個標題這里新增一個實時的統計資料,來展示目前還有多少未完成的待辦事項,

image

結合ASP.NET Core SignalR和Blazor實作聊天室應用

https://github.com/CraigTaylor/BlazorServerSignalRApp

0. 什么是SignalR?

ASP.NET Core SignalR是一種開放源代碼庫,可簡化將實時web功能添加到應用程式的功能,實時web功能使服務器端代碼可以立即將內容推送到客戶端,

適用于SignalR

  • 需要從服務器進行高頻率更新的應用,示例包括游戲、社交網路、投票、拍賣、地圖和GPS應用,
  • 儀表板和監視應用,示例包括公司儀表板、即時銷售更新或旅行警報,
  • 協作應用,協作應用的示例包括白板應用和團隊會議軟體,
  • 需要通知的應用,社交網路、電子郵件、聊天、游戲、旅行警報和很多其他應用都需使用通知,

SignalR提供一個API,用于創建(RPC)的服務器到客戶端遠程程序呼叫,Rpc通過服務器端.NET Core代碼從客戶端呼叫JavaScript函式,

下面是的某些功能SignalR ASP.NET Core

  • 自動處理連接管理,
  • 將訊息同時發送到所有連接的客戶端,例如,聊天室,
  • 向特定客戶端或客戶端組發送訊息,
  • 可縮放以處理不斷增加的流量,

SignalR支持以下用于按正常回退)(處理實時通信的技術:

  • WebSockets
  • Server-Sent事件
  • 長輪詢

SignalR自動選擇服務器和客戶端功能內的最佳傳輸方法,

1. 先準備".NET SDK (Software Development Kit)"環境,

如果之前沒有安裝的先安裝這個SDK:

dotnet-sdk-5.0.202-win-x64.exe

如果已經安裝過了,檢查下當前版本:

dotnet --version

image

2. 創建Blazor Server應用

dotnet new blazorserver -o BlazorServerSignalRApp --no-https

通過DOTNET-CLI腳本的New命令,新建一個blazorserver專案模板,輸出目錄命名為BlazorServerSignalRApp,同時不需要HTTPS的支持,

image

3. 給專案添加"SignalR"客戶端庫

cd .\BlazorServerSignalRApp\
dotnet add package Microsoft.AspNetCore.SignalR.Client

image

先要切換到BlazorServerSignalRApp這個專案目錄,然后通過dotnet add package $packageName來添加Microsoft.AspNetCore.SignalR.Client這個依賴包,

image

ASP.NET Core SignalR .Net客戶端 - 通過ASP.NET Core SignalR .net客戶端庫,你可以SignalR從.net應用程式與中心進行通信,

4. 創建對話集線器Hub

在專案根目錄,創建新目錄Hubs,用來存放集線器,

mkdir Hubs

image

然后新建集線器,用來承載對話,

在vsc中,在Hubs檔案夾上右鍵,選擇New C# Class,新建一個名為ChatHub的集線器模型類,

image

接下來,我們添加對ChatHub的實作,需要參考System.Threading.TasksMicrosoft.AspNetCore.SignalR,添加一個SendMessgae的方法來支持發送訊息業務,向所有的連接客戶端都發送訊息,

using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;

namespace BlazorServerSignalRApp.Hubs
{
    /// <summary>
    /// 對話集線器
    /// </summary>
    public class ChatHub : Hub
    {
        /// <summary>
        /// 發送訊息
        /// </summary>
        /// <param name="user"></param>
        /// <param name="messgae"></param>
        /// <returns></returns>
        public async Task SendMessage(string user, string messgae)
        {
            // 向全部連接客戶端發送指定訊息
            await Clients.All.SendAsync("ReceiveMessage", user, messgae);
        }
    }
}

5. 為SignalR中心添加服務和終結點

前往Startup.cs中,在ConfigureServices函式中,配置我們需要的服務,這里主要是添加AddSignalR(SignalR服務)和AddResponseCompression(回應壓縮中間件服務),這里需要添加下對Microsoft.AspNetCore.ResponseCompression的參考,

public void ConfigureServices(IServiceCollection services)
{
    // 添加SignalR服務
    services.AddSignalR();
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    // 添加回應壓縮中間件服務
    services.AddResponseCompression(opts =>
    {
        opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/octet-stream" });
    });
}

image

Configure函式中,在頂部添加UseResponseCompression,使用處理管道的配置頂部的“回應壓縮中間件”,

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 使用回應壓縮中間件
    app.UseResponseCompression();
}

Configure函式中,找到app.UseEndpoints添加新的終結點,在映射Blazor中心的終結點和客戶端回退之間,為中心添加一個終結點,

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        // 添加對話集線器終結點
        endpoints.MapHub<ChatHub>("/chathub");
        endpoints.MapFallbackToPage("/_Host");
    });
}

image

6. 添加用于聊天的Razor組件代碼

有了前面的基礎,接下來,我們構建下聊天對話的前端組件代碼,

dotnet new razorcomponent -n Chat -o Pages

然后,通過DOTNET-CLI腳本的New命令,輸出目錄為Pages,新建名為Chatrazorcomponent組件,

image

然后我們得到了Chat.razor這個組件,我們思考下聊天室這個業務場景里面,我們需要兩個輸入框和一個發送按鈕來承載發送新訊息的作用,同時我們還需要一個展示已收到訊息的串列,

<h3>聊天室</h3>

<div >
    <label>
        發送用戶:
        <input @bind="User">
    </label>
</div>

<div >
    <label>
        訊息內容:
        <input @bind="Message">
    </label>
</div>

<button @onclick="SendMessage">發送</button>

<br/>

<ul>
    @foreach (var message in MessageList)
    {
        <li>@message</li>
    }
</ul>

@code {

    /// <summary>
    /// 發送用戶
    /// </summary>
    /// <value></value>
    public string User { get; set; }

    /// <summary>
    /// 訊息內容
    /// </summary>
    /// <value></value>
    public string Message { get; set; }

    /// <summary>
    /// 發送訊息
    /// </summary>
    public void SendMessage(){

    }

    /// <summary>
    /// 訊息串列
    /// </summary>
    /// <typeparam name="string"></typeparam>
    /// <returns></returns>
    public List<string> MessageList { get; set; } = new List<string>();
}

為了查看效果,我們添加Chat.razor組件到Index.razor頁面,

@page "/"

<Chat />

image

通過dotnet watch run先看下效果,

image

7. 在Razor組件中連接聊天集線器

有了前面的界面基礎,接下來我們在Chat.razor組件中先開始做連接集線器和接收訊息的支持,

我們需要在組件啟動的時候就開始指定連接,所以這里我們用到一個組件的函式OnInitializedAsync,我們通過重寫實作它來連接集線器,

ComponentBase.OnInitializedAsync方法,當組件準備好啟動時呼叫的方法,它從呈現樹中的父級接收了其初始引數,需要支持異步的時候,優先使用OnInitializedAsync

與此同時,我們還需要新建一個HubConnection型別的hubConnection私有物件,來承載連接,

/// <summary>
/// 集線器連接
/// </summary>
/// <value></value>
private HubConnection hubConnection { get; set; }

為了查看到連接效果,我們可以設定一個連接狀態IsConnectioned,來獲取連接狀態,

/// <summary>
/// 是否已連上集線器
/// </summary>
private bool IsConnectioned => 
    hubConnection != null ? 
    hubConnection.State == HubConnectionState.Connected : 
    false;

然后我們將IsConnectioned的值和發送按鈕的可用狀態進行系結,

<button @onclick="SendMessage" disabled="@(!IsConnectioned)" >發送</button>

接下來,我們重寫OnInitializedAsync來完成對集線器的定義和連接,

/// <summary>
/// 組件初始化完成事件
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
    // 定義聊天集線器(含終結點)
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
        .WithAutomaticReconnect()
        .Build();

    // 開始連接集線器
    await hubConnection.StartAsync();
}

運行查看效果:

image

這里我們在HubConnection使用了WithAutomaticReconnect這個自動斷開重連配置,在沒有任何引數的情況下,會WithAutomaticReconnect()將客戶端配置為分別等待0、2、10和30秒,然后再嘗試重新連接嘗試,

8. 在Razor組件中接收聊天集線器訊息

Chat.razor組件的OnInitializedAsync中,我們順帶異步接收下集線器訊息,這里注意的是,接收訊息的方法名稱必須和ChatHubSendAsync時定義的方法名稱保持一致,

另外,需要注意的是,接收到新訊息之后,我們添加到新訊息到MessageList中之后,我們需要通過StateHasChanged來主動觸發一次通知變更,

/// <summary>
/// 組件初始化完成事件
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
    // 定義聊天集線器(含終結點)
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
        .WithAutomaticReconnect()
        .Build();

    // 接收集線器訊息
    hubConnection.On<string, string>("ReceiveMessage", (user, message)=>
    {
        var messageItem = $"發送用戶:{user},訊息內容:{message}";
        MessageList.Add(messageItem);
        StateHasChanged();
    });

    // 開始連接集線器
    await hubConnection.StartAsync();
}

image

9. 在Razor組件中發送新聊天訊息

之前在Chat.razor組件中,我們已經預留了一個SendMessage方法,接下來我們來完善它,

首先我們判斷UserMessage是否填寫,填寫了之后,我們直接呼叫當前連接的SendAsync即可,這里需要注意的是,方法引數名稱這里需要和ChatHub中函式名稱保持一致,

/// <summary>
/// 發送訊息
/// </summary>
public async Task SendMessage()
{
    // 判斷輸入是否合法
    if(!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Message))
    {
        // 發送訊息,方法名為"SendMessage",和"ChatHub"中方法名定義需一致,
        await hubConnection.SendAsync("SendMessage", User, Message);
    }
}

image

運行下,看下效果:

image

10. 退出Razor組件時關閉集線器連接

我們可以使用DisposeAsync方法來在退出組件時,主動關閉集線器連接,這里需要參考@implements IAsyncDisposable

@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
/// <summary>
/// 退出組件銷毀時事件
/// </summary>
/// <returns></returns>
public async ValueTask DisposeAsync()
{
    // 關閉連接
    await hubConnection.DisposeAsync();
}

image

11. 跨多個客戶端在聊天室測驗群發訊息

dotnet watch run

運行之后,我們把網址復制下,打開三個瀏覽器視窗,這時候,每一個視窗就是個獨立的客戶端,我們試著在一個視窗發送訊息,發現這時候,三個視窗都可以順利收到訊息,說明群發成功,

image

我們可以試著在第二個、第三個聊天室發送訊息,發現都可以順利收到,哈哈,是不是很開心,

image

參考

  • ASP.NET Core Blazor 簡介
  • BlazorFluentUI
  • Blazor Webassembly Demo
  • DOM概述
  • 在傳統 Web 應用和單頁應用 (SPA) 之間選擇
  • WebAssembly (abbreviated Wasm)
  • 從 ASP.NET Core Blazor 中的 JavaScript 函式呼叫 .NET 方法
  • 在 ASP.NET Core Blazor 中從 .NET 方法呼叫 JavaScript 函式
  • ASP.NET Core Blazor 托管模型
  • 用于 ASP.NET Core Blazor 的工具
  • Blazor Tutorial - Build your first Blazor app
  • 生成 Blazor 待辦事項串列應用
  • C# Extensions - VS MarketPlace
  • C# XML Documentation Comments - VS MarketPlace
  • BlazorApp
  • TodoList
  • 結合使用 ASP.NET Core SignalR 和 Blazor
  • ASP.NET Core SignalR 簡介
  • ASP.NET Core SignalR .Net 客戶端
  • ComponentBase.OnInitializedAsync 方法
  • 通過Blazor使用C#開發SPA單頁面應用程式(3)

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/282858.html

標籤:.NET Core

上一篇:MVC之實作基于token的認證

下一篇:c#使用Aspose列印檔案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more