Blazor tutorial for beginners - part 1
以 Blazor Server App 入門教學為主。
引言
為此 Blazor Fundamentals Tutorial 教學系列影片(於2019年第四季)練習與筆記。 發現另一個 Blazor tutorial for beginners 教學系列影片(於2021年第一季)。 這一套也可以 Blazor C# Tutorials 教學系列影片(於2020年五月)。
Blazor server-side vs client-side (WebAssembly) | What should you choose?
Blazor WASM App
Blazor Server App
Pros
(優點)
No need for a server side
No need for JavaScript
Offline support
No need for JavaScript
Small size, fast loading
You are using .NET Core
Debug like a boss
Runs on any browser
Your code stats on the server
Each browser session is an open SignalR connection
Cons
(弱點)
Big donwload size
Requires WebAssembly
Less mature runtime
Debugging is not great
Still in preview
Your DLLs are downloaded
You need a server-side
No offline support
Highher latency, worse UX
Hard to maintain and scale
Scaling SignalR in non-Azure fashion is pretty hard
What are Razor Components? | Blazor Tutorial 1
Requirements
C# 9.0
Razor Pages (.cshtml)
React Component
Can be
Nested
Reused
Packaged
Each component has its own state.
Components can have Parameters.
How to supply parameters to the component?
Assign them upon reference.
[Parameter]
,ChildContent
, etc來自上層元件
[CascadingParameter]
、CascadingValue
來自祖宗層元件
Provide them via Routing.
就是 URL 挾帶的參數
Razor components support Data Binding
We can bind object to DOM elements and have object changes manipulate the DOM to reflect those changes in the state.
Razor components have lifecycle related methods.
OnInitialized(), OnInitializedAsync()
元件初始化
OnParmetersSet(), OnParametersSetAsync()
元件參數值已變更。
在 render 之前,在 initalize 之後。
在父層刷新子層元件時。
OnAfterRender(bool firstRender), OnAfterRenderAsync(bool firstRender)
UI 已更新。
關於 firstRender 與 OnInitialize 在應用上的差別
一般的元件狀態值初始化可在 Initialize 執行。若是要存取前端的資源,如:LocalStorage
, SessionStorage
, Timer
的初始化需在此 firstRender 執行。
Components can be disposable
@implements IDisposable
Razor 元件入門範例
<style>
.title {
font-size:2em;
}
</style>
<h4 class="title">Parent Component</h4>
<div class="alert alert-success">
@AlertText
</div>
<ChildComponent>我是次要訊息</ChildComponent>
@code{
[Parameter]
public string AlertText { get; set; }
}
<h4>Child Component</h4>
<div class="alert alert-info">
@ChildContent
</div>
<div class="alert alert-@alertTheme">
<label>
<input type="checkbox" @bind="f_darkTheme" /> Toggle dark theme
</label>
</div>
@code{
bool f_darkTheme;
private string alertTheme => f_darkTheme ? "dark" : "light";
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized()
{
f_darkTheme = true;
}
}
Timer 範例
@page "/timer-counter"
@implements IDisposable ///------ ※ disposable
@using System.Timers
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
/// ※ timer 的初始化在 firstRender 執行
if (firstRender)
{
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimerInterval;
timer.AutoReset = true;
// Start the timer
timer.Enabled = true;
}
}
public void Dispose()
{
/// ※ 別忘記要釋放 timer 否則會變成無人管理的流浪資源。
timer?.Dispose();
// During prerender, this component is rendered without calling OnAfterRender and then immediately disposed
// this mean timer will be null so we have to check for null or use the Null-conditional operator ?
}
private void OnTimerInterval(object sender, ElapsedEventArgs e)
{
IncrementCount();
}
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Count incremented: {currentCount}");
}
}
補充:Blazor Server App 啟動順序
以下為 Blazor App 在 .NET5 版本的啟動順序,.NET6 把 Startup.cs 併入 Program.cs 原則上啟動順序是一樣的。
Program.cs
system entry door.
load
appsettings.json
.
Startup.cs
register service
configure wetsite.
_Host.cshtml
import website resource and assets.
App.razor
authorization
page routing
MainLayout.razor
ready to run.
補充:Blazor Component 完整說明
完整的官方文件說明 Blazor Component (官方名稱:Razor Component),為必讀文章。
Quoting Introduction to Razor Pages in ASP.NET Core:
Razor Pages can make coding page-focused scenarios easier and more productive than using controllers and views.
As you can see on tree structure, a razor page is a cshtmlfile (template) plus a
cs` file (behavior). The page is rendered as html and send to navigator.
Exists another kind of apps, blazor. Quoting Introduction to ASP.NET Core Blazor:
Blazor is a framework for building interactive client-side web UI with .NET
Important term "interactive", not only render html, is a language to make page interactive (no just render html on server and send it to client)
請直接參考附件。
補充:ASP.NET Core Blazor 資料系結(data-binding)
請直接參考附件。
Events up 範例
Props Down, Events Up現在依然是前端開發的父子層基本溝通機制。
在Blazor App中 Props Down的實作就是用Parameter
傳遞參數到子元件。
Events Up的實作則是 EventCallback<TValue>
的傳遞與向上invoke。
這裡只展示events up 的範例。
@page "/foo-page"
@inject IJSRuntime jsr
<MyEvent OnMyMessage=HandleMyMessage />
@code{
void HandleMyMessage(MyMessage msg)
{
jsr.InvokeVoidAsync("alert", $"MyMessage:{msg.code}:{msg.msg}");
}
}
<button @onclick="HandleClick">送出我的訊息</button>
@code{
@* 接受父層的EventCallback event 參數 *@
[Parameter] public EventCallback<MyMessage> OnMyMessage { get; set; }
void HandleClick()
{
MyMessage msg = new() {
code = 6,
msg = "我很讚"
};
@* events up:將訊息往上送給父層 *@
OnMyMessage.InvokeAsync(msg);
}
}
共用資源
// resource
public class MyMessage
{
public int code { get; set; }
public string msg { get; set; }
}
Data-Binding 範例程式碼
雙向繫結範例
@* 基本 binding *@
<input @bind="value" />
<p>@value</p>
@code{
string value = "123";
}
雙向繫結(2-way binding)運作原理除依循「Properties down, events up」原則外還加入一些其他規則。
@* 進一步設定 binding *@
<input @bind="value" @bind:event="onchange"/>
<code>@value2</code>
@* 可以把event從預設的 onchange 換成其他 oninput 等等。
<input @bind="value" @bind:event="oninput" />
*@
@code{
string value = "123";
}
自訂輸入元件 Data-Binding
@page "/foo-page"
@* 自訂繫結範例 *@
@* 自訂繫結支援 2-way binding 語法 *@
<MyInput @bind-MyValue=value1 />
@* 也支援 1-way binding 語法 *@
<MyInput MyValue=value2 MyValueChanged=HandleMyValueChanged />
<p>@value1, @value2</p>
@code{
// State
decimal value1;
decimal value2;
void HandleMyValueChanged(decimal value)
{
value2 = value;
}
}
<input type="number" value="@MyValue" @onchange="HandleChange" />
@code{
@* ※自訂繫結的參數為一對,有命名規則,event的名稱需是field名稱加'Changed' *@
[Parameter] public decimal MyValue { get; set; } = 0m;
[Parameter] public EventCallback<decimal> MyValueChanged { get; set; }
void HandleChange(ChangeEventArgs e)
{
MyValueChanged.InvokeAsync(e.Value == null ? 0m : decimal.Parse((string)e.Value));
}
}
Dependency Injection | Blazor Tutorial 2
Dependency Injection (DI)
MVC6 引入DI 機制為基礎框架之一。用一句話說明:讓 service
可以隨時隨地 @inject
。
DI 物件的生命週期 (※必考題)
Transient (短暫的)
The classs is instantiated once per service resolution.
每當
service
被解譯使用時建構一次。用於數據交易,如:database transaction。
Singleton (唯一的)
The class is instantiated once for the whole application.
Scoped (有範圍的)
The class is instantiated once per scope.
scope? 與其引用者生命週期一致。
DI 物件生命週期預設是 scoped。
※ scoped in WebAssembly Blazor works like Singleton.
常用的 service
HttpClient
used to call APIs
IJSRuntime
used in JS Interop
NavigationManager
used in navigation and routing
一個簡單的 Service 註冊與 @inject 範例
@page "/showcase"
@inject RandomService randomSvc /// 插入 RandomService
<div class="alert alert-danger">
My random id is: @randomSvc.RandomId
</div>
@code{
...
}
using System;
namespace BlazorServerTutor.Services
{
public class RandomService
{
public Guid RandomId => Guid.NewGuid();
}
}
using ...
using BlazorServerTutor.Services;
namespace BlazorServerTutor
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
// 註冊 service
services.AddScoped<RandomService>();
//services.AddSingleton<RandomService>();
//services.AddTransient<RandomService>();
...
}
...
}
}
補充:ASP.NET Core - Dependency Injection
關於註冊 service 至少要一門專篇,DI 編程又是另一門專篇,故不在入門期深入討論。@inject 的標的不只是 Razor Component, 也可以是一般的 Class,injection 的插入點可以是 constructor
(建構式), method
(成員函式), property
(成員屬性) 都可以。
What are Blazor Layouts? | Blazor Tutorial 3
Everything starts with the Default Layout
In order to turn a componnet into a Layout you need to:
1) Inhert from LayoutComponentBase
2) Use the @Body
parameter
Blazor Server App 啟動執行順序 (※必考題)
Program.cs
載入
appsettings.json
Startup.cs
ConfigureServices()
,註冊 serviceConfigure()
,網站啟動程序
Pages/_Host.cshtml
載入網頁靜態資源,如: MudBlazor, Bootstrap 5 等等
App.razor
導引以可以動態載入資源
routing to
@page
Shared/MainLayout.razor
default layout
@Body
MainLayout 預設程式碼
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>
可在@page
指定layout。
@page "/showcase"
@layout NoLayout //------ 可在Page指定layout
@inject RandomService randomSvc
<h1>Showcase</h1>
@code{
...
}
Routing and Navigation | Blazor Tutorial 4
Page Routing --- 直接讀碼
@page "/counter/{StartingCount:int?}" ///←--- 有參數的URL
<h1>Counter</h1>
<p>展示 @page routing。</p>
<p class="my-counter">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
using Microsoft.AspNetCore.Components;
namespace BlazorServerTutor.Pages
{
public partial class Counter
{
/// 此參數將由URL或上層元件取得後更新。
[Parameter]
public int StartingCount { get; set; } = 0; ///←--- 元件外部參數
private int currentCount; ///←--- 元件內部狀態
protected override void OnInitialized()
{
currentCount = StartingCount; ///←--- 依外部參數初始化
}
private void IncrementCount()
{
currentCount++;
}
}
}
routing 詳情請直接看參考資料。
Navigation Support
<NavLink/>
元件@inject NavigationManager
返回首頁範例
@inject NavigationManager navMan
<button class="btn btn-primary" @onclick="MoveToHome" >
返回首頁
</button>
@code{
private void MoveToHome()
{
navMan.NavigateTo("/"); /// 返回首頁。
}
}
Last updated