1. 程式人生 > >Blazor入門:ASP.NET Core Razor 元件

Blazor入門:ASP.NET Core Razor 元件

[TOC] 官方文件原文位置: https://docs.microsoft.com/zh-cn/aspnet/core/blazor/components?view=aspnetcore-3.1 本文並不是獨立教程的文章,而是屬於對微軟文件的講解和說明。 元件:專案 Blazor 中,使用 `.razor` 結尾的檔案,稱為元件;而 Blazor 中的元件,正式名稱是 **razor 元件**; Blazor 元件是 razor 過渡而來的,使用 razor 的基本語法特性,但是 Balzor 不支援 razor 中的標記幫助程式。 ### 關於元件 `.razor` 檔案分為頁面(帶`@page`)和元件(不帶`@page`,或者說頁面元件和非頁面元件。兩者區別在於頁面有路由,可以直接通過 URI 訪問,一般放在 Page 資料夾中;而元件,作為一個部件,必須嵌入其它元件中,在頁面中顯示,一般放到 Shared 資料夾中,供多個頁面共享、複用。 本文接下來所指的元件都是非頁面元件。 `.razor` 檔案中,開頭有 `@page` 標記的,就是頁面元件,沒有的就是非頁面元件。 當然兩者並沒有嚴格的區分。 元件命名時,應該帶上 `Component` 字尾。 ### 元件類 每個 `.razor` 檔案,在編譯後會生成一個類,稱為元件類。 生成的類的名稱與檔名匹配。 因此,每個 `.razor` 檔案,必須以大寫字母開頭,按照類名命名規範定義檔名稱。
`.razor` ,以 `@code{}` 包含 C# 程式碼,這部分程式碼除了元件間可以使用,程式中也可以正常使用,因為屬於類的一部分。 建立 `Test.razor` 檔案,檔案內容如下: ```csharp @code{ public string Name { get; set; } } ``` Pargrom 中: ```csharp Pages.Test test = new Pages.Test(); test.Name = "Blazor"; ``` 簡單來說,就是可以作為一個類來使用。`@code{}` 中定義的成員,就是類的成員。 成員正常使用 public 、private 等訪問修飾符修飾。 ### 靜態資產 預設靜態資原始檔位置在專案的 wwwroot 目錄,前端(.razor、.cshtml)等,預設定址時,使用絕對路徑 `/` 即可訪問資源。 例如: ```html
``` 這個路徑是要放到前端才能,由前端訪問時 ASP.NET Core 框架自動處理,相當於前端訪問 `/` ,後端訪問 `D:/test/Blazor/wwwroot`。 ### 路由與路由引數 頁面元件使用 `@page` 設定此頁面的訪問地址,這裡沒有 Controller 和 Action 的分層和路由導航(相對地址),直接是一個絕對的訪問地址,並且全域性唯一。 `Index.razor` 中,路由: ``` @page "/" ```
Blazor 不支援像 Controller 和 Action 那樣設定靈活的 URL 可選引數(URL Query),例如: ```csharp [HttpGet("Test/{Id}")] public string Test([FromQuery]int Id) { return "123"; } ``` Blazor 如果想通過 URL Query 傳遞引數,可以使用 `{`Name`}`: ```html @page "/test" @page "/test/{Id}"

@Id

@code{ [Parameter] public string Id { get; set; } = "123"; } ``` 因為 Blazor 不支援可選引數,因此,如果只設置 `@page "/test/{Id}"`,那麼每次訪問都必須帶有這個引數值。 需要使用 `[Parameter]` 來修飾成員,才能捕獲 `@page "/test/{Id}"`。
另外,理由引數是 string 型別,不能自動轉為數值型別。不如會報錯: ```csharp InvalidOperationException: Unable to set property 'Id' on object of type 'BlazorApp1.Pages.Test'. The error was: Unable to cast object of type 'System.String' to type 'System.Int32'. ``` 你可以接收後,顯式轉為數值型別。 ### 元件引數 在 `@code` 程式碼塊中,使用 ` [Parameter] ` 修飾的公共屬性,那麼這個屬性就會標識為元件指定引數。 注意官網文件中,這個小節的程式碼示例,實際是不允許這樣寫得的。 目前,有兩個地方需要使用 `[Parameter]` 特性,一個是前一小節的路由引數繫結,另一個是嵌入元件時使用。 示例: Test.razor 檔案內容: ```csharp

@Title

@code{ [Parameter] public string Title { get; set; } = "test"; } ``` 別的元件嵌入 `Test.razor` 這個元件時,就可以使用 Title 傳遞引數進去: ```csharp ``` ### 請勿建立會寫入其自己的組引數屬性的元件 前面我們說到, `[Parameter]` 特性的使用,這個特性時作為引數傳遞而使用的。 對於路由引數,其修飾的屬性應該是 `privite`,對於其它元件傳遞引數,屬性應該設定為 `public`。 如果一個元件的 `@code{}` 成員不需要被外界作為引數使用,就應該設定為 `private`。 因為 `.razor` 一般不會作為類來使用。、;而且不設定 `[Parameter]` 的屬性,別的元件也使用不了這個屬性。 那麼,文件說 “請勿建立會寫入其自己的組引數屬性的元件”,指定是 `[Parmeter]` 休息的屬性,是作為引數傳遞使用的,不要在元件中修改這個屬性的值。 如果實在要操作的話,可以先拷貝這個值,使用別的變數操作,示例: ```csharp

@Title

@code{ [Parameter] public string Title { get; set; } = "test"; private string _Title; protected override void OnInitialized() { _Title = Title; } } ``` 這樣,元件要操作的話,可以使用 `_Title` ,保留 `Title`。 `OnInitalized()` 是一個元件初始化的方法,也可以理解成建構函式,可以參考 https://docs.microsoft.com/zh-cn/aspnet/core/blazor/lifecycle?view=aspnetcore-3.1#component-initialization-methods ### 子內容 因為元件是可以巢狀的,可以要求另一個元件顯示要求的內容。 - 被多個元件使用,不同元件要呈現不一樣的內容; - 要根據父元件的配置,顯示子元件; - 元件 A 要求使用到的元件 B,顯示其傳遞的內容; 簡單來說,就是將頁面內容作為複雜型別傳遞給另一個元件,要求這個元件顯示出來。 那麼,子內容指的是一個元件可以接收另一個元件的內容,使用 `RenderFragment` 來接收內容。 示例如下: `Test.razor` 中,內容: ```csharp @Children @code{ [Parameter] public RenderFragment Children { get; set; } } ``` 另一個元件: ```csharp @page "/" @code{ private RenderFragment r =@

測試子內容

; } ``` `RenderFragment` 的使用,請自行查閱資料。 ### 屬性展開 屬性展開是使用字典型別表示一個 Html 標籤的多個屬性。 ```csharp @code { #region private string Maxlength { get; set; } = "10"; private string Placeholder { get; set; } = "Input placeholder text"; private string Required { get; set; } = "required"; private string Size { get; set; } = "50"; #endregion // 使用字典鍵值對錶示 public Dictionary InputAttributes { get; set; } = new Dictionary() { { "maxlength", "10" }, { "placeholder", "Input placeholder text" }, { "required", "required" }, { "size", "50" } }; } ``` ### 任意引數 `[Paramter]` 特性,只有一個屬性,其定義如下: ```csharp public bool CaptureUnmatchedValues { get; set; } ``` 文件說明:[Parameter] 上的 CaptureUnmatchedValues 屬性允許引數匹配所有不匹配任何其他引數的特性。 其作用是通過字典接收在父元件中出現但是未在 `@code{}` 中定義的引數屬性。 例如: `Test.razor` 中 ```csharp @code{ // 這個屬性沒有用,隨便起個名字測試 [Parameter] public string A { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IDictionary AdditionalAttributes { get; set; } } ``` 父元件中使用: ```csharp ``` B、C 都是 `Test.razor` 中沒有出現過的,那麼這些引數和引數值都會自動轉為鍵值對儲存到 AdditionalAttributes 中。 測試示例: `Test.razor` 中的內容 ```csharp
    @foreach (var item in AdditionalAttributes) {
  • @item.Key - @item.Value
  • }
@code{ // 這個屬性沒有用,隨便起個名字測試 [Parameter] public string TTT { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IDictionary AdditionalAttributes { get; set; } } ``` 其它元件使用: ```csharp @page "/" ``` ### 捕獲對元件的引用 元件引用提供了一種引用元件例項的方法,使用 `@ref` 可以實現引用對引數的引用。 建立一個 `Test.razor` 檔案,內容不限。 在一個元件中,引用該元件例項 ```csharp @page "/" @code{ private Test _test; } ``` 在使用 `Test.razor` 元件的同時,保留了引用,以便在 `@code{}` 中使用其成員。 ### 在外部呼叫元件方法以更新狀態 元件繼承了 ComponentBase 型別,有個 `InvokeAsync` 方法可用於外界更新此 UI 的狀態。 示例如下: 建立 MyUIServer 型別, ```csharp // 能夠向所有正在開啟的 Index.razor 頁面傳送通知 public static class MyUIServer { // 向所有人傳送通知 public static async Task ToMessage(string message) { if (events != null) { await events.Invoke(message); } } public static void AddEvent(Func func) { events += func; } public static void RemoveEvent(Func func) { events -= func; } private static event Func events; } ``` 在 `Index.razor` 中 ```csharp @page "/" @using BlazorApp1.Data @implements IDisposable
    @foreach (var item in messageList) {
  • @item
  • }
@code { private string _message; private List messageList = new List(); // 進入頁面時 protected override void OnInitialized() { MyUIServer.AddEvent(UIEvent); } // 退出當前頁面UI後移除該事件 public void Dispose() { MyUIServer.RemoveEvent(UIEvent); } protected async Task UIEvent(string message) { // 元件自帶的方法,用於外部呼叫更新狀態 await InvokeAsync(() => { messageList.Add(message); StateHasChanged(); }); } // 向所有正在訪問 Index.razor 頁面傳送訊息 private async Task Btn() { await MyUIServer.ToMessage(_message); } } ``` 開啟多個視窗,訪問頁面 `https://localhost:5001/`,在其中一個視窗輸入內容並且點選按鈕,即可將訊息內容推送到其它視窗。 下面是一個修改官網示例的示例: 建立一個型別 NotifierService ```csharp public class NotifierService { public async Task Update(string key, int value) { if (Notify != null) { await Notify.Invoke(key, value); } } public event Func Notify; } ``` 該型別的 Notify 可以繫結多個事件;通過呼叫 `Update()` 方法,可以觸發各個事件。 在 Startup 中注入服務 `services.AddSingleton();`。 `Index.razor` 中,內容為: ```csharp @page "/" @using BlazorApp1.Data @inject NotifierService Notifier @implements IDisposable

Last update: @_lastNotification.key = @_lastNotification.value

@code { private (string key, int value) _lastNotification; protected override void OnInitialized() { Notifier.Notify += OnNotify; } public async Task OnNotify(string key, int value) { // 元件自帶的方法,用於外部呼叫更新狀態 await InvokeAsync(() => { _lastNotification = (key, value); StateHasChanged(); }); } // 退出當前頁面UI後移除該事件 public void Dispose() { Notifier.Notify -= OnNotify; } } ``` `Test.razor` 檔案中: ```csharp @page "/test" @using BlazorApp1.Data @inject NotifierService Notifier Key: Value: @code{ private string Key { get; set; } private int? Value { get; set; } private async Task Update() { await Notifier.Update(Key, Value.Value); Key = string.Empty; Value = null; } } ``` 然後啟動專案,一個頁面開啟 `https://localhost:5001/` ,另一個頁面開啟 `https://localhost:5001/test`。 在 `test` 頁面輸入 Key 和 Value,點選按鈕,即可通知到所有正在開啟 `Index.razor` 的頁面。 ### 使用 @ 鍵控制是否保留元素和元件 在使用表格或了表等元素時,如果出現插入或刪除、更新等情況,整個表格或列表,就會被重新渲染。這樣會帶來比較大的效能消耗。 一般使用繫結的元素,其更新是自動的,不需要人為控制。 在能保證每一項的某個元素列,都是唯一的時候,我們可以使用 `@key` 關鍵字來優化元件。 示例: ```csharp @page "/" @using BlazorApp1.Data Key: Value:
    @foreach (var item in dic) {
  • @item.Key - @item.Value
  • }
@code { private int? _key; private int _value; private List dic { get; set; } = new List(); private void Add() { if (_key == null) return; dic.Add(new MyData { Key = _key.Value, Value = _value }); _key = null; } private void Remove() { if (_key == null) return; dic.Remove(dic.First(x => x.Key == _key.Value)); _key = null; } } ``` ### 指定基類 `@inherits` 指令可用於指定元件的基類。 元件都預設繼承了 ComponentBase 。 示例: 建立檔案 `TestBase.razor` ,內容如下 ```csharp @code{ protected int Id { get; set; } } ``` 建立 `Test.razor` ,檔案內容如下 ```csharp @inherits TestBase @code{ public int Get() { return Id; } } ``` ### 指定屬性 可以通過 @attribute 指令在 Razor 元件中指定元件的特性(屬性)。 例如頁面需要登入才能訪問,則新增 `[Authorize]` 。 ```html @page "/" @attribute [Authorize] ``` ### 匯入元件 當要使用的元件與當前元件在同一個名稱空間時,不需要“匯入”,如果兩者不在同一個名稱空間,則可以使用 `@using` 匯入此元件。 ### 原始 HTML 使用 MarkupString 型別可以將字串轉為 HTML 元素物件。 ```csharp @html @code{ public MarkupString html = (MarkupString)"

Test

"