Blazer! Blazer! Blazor is a series of Blazor Zero Basics-Beginners videos that I co-produced with Prof. Shanyou Zhang. This series enables programmers who have never been exposed to Blazor to master the ability to develop Blazor applications.
Video Address:
https://space.bilibili.com/48…
This series of articles is based on the Blazor! Live content compilation, upgrade. Net5, improved problems, more comprehensive explanation. Due to the limited space, part of the code is omitted in this article. The complete example code is as follows:
https://github.com/TimChen44/…Contributor of Ant Design Blazor project, with more than 10 years of experience, long term based on. NET technology stack architecture and product development work, now working in Chint Group. Email: [email protected] Welcome readers to contact me if you have any questions, we make progress together.
In this post, I want to talk about the essence of Blazor and what I personally consider to be the best feature of the Blazor framework — its components.
component
Components are simple encapsulations of data and methods. Almost all UI-related frameworks have the concept of components (controls).
Early Delphi components called VCL (Visual Component Library), it uses its own nested way to compose the required user interface, and provides properties, methods, events and components external interaction, its own has an independent life cycle, when necessary to destroy.
Since then,.NET WinForms and WPF components have almost the same definition and purpose, although the design and implementation of the components are completely different from Delphi.
Today, the Web front-end framework Angular also uses the concept of components, and the overall concept remains similar.
Some frameworks, such as Delphi and WinForms, divide components into invisible components and visible controls based on whether they are visible or not
Looking at the component design of these frameworks, the components can be distilled to include the following features.
Blazor applications are also built using components. A component is a self-contained user interface (UI) block, such as a page, dialog box, or form. The component contains the HTML markup and processing logic needed to insert data or respond to UI events. Components are very flexible and lightweight. It can be nested, reused, and shared between projects.
1. Parameters (attributes)
Provides a way to pass data from outside a component to inside a component.
In Blazor, we call a component’s Property a Parameter. A Parameter is itself an attribute, but in order for the Blazor framework to distinguish between the two, we add the [Parameter] Property to the attribute to declare the Property as a Parameter to the component.
[Parameter]
public string Text { get; set; }
Component parameters
Component parameters can receive values given in the Razor page and can support simple or complex types.
<! > <h1>Blazor is @text! </h1> @code { [Parameter] public string Text { get; set; }}
<! > <Component Title="Superior">
In the example above, you pass the Superior argument to the component, and the component will print Blazor is Superior!
Routing parameters
The component can receive routing parameters from the routing template provided by the @Page directive. The router uses routing parameters to populate the corresponding component parameters. Parameter types are limited by routing rules and only a few basic types are supported.
<! -- >@page "/RouteParameter/{text}" <h1>Blazor is @text! </h1> @code { [Parameter] public string Text { get; set; }}
When routing using the /RouteParameter/Superior address, jump to the page in the example above, and the page prints Blazor is Superior!
Cascade parameters
In some cases, using component parameters to flow data from an ancestor component to a descendant component is inconvenient, especially if there are multiple component layers. Cascading values and parameters solves this problem by providing a convenient way for an ancestor component to provide values for all of its children.
The ancestor component uses CascadingValue to set the cascade value that needs to be passed down, and the descendant component uses the property [CascadingParameter] to declare the cascade parameter to receive the cascade value.
This feature will be explained in a detailed Demo later in this article, but will not be explored here.
2. The event
An event is a mechanism that is initiated internally by a component and processed externally by the component.
There are some subtle differences in the use of events between the original HTML element and the Razor component, which are described below.
The Html element
The EVENT of an HTML element is handled in the @On {EVENT} format (for example, @onclick). The Razor component treats the value of this attribute as an EVENT handler.
<h1>Blazor is @Text!</h1>
<button @onclick="OnClick">Button</button>
@code
{
private string Text { get; set; }
void OnClick(MouseEventArgs e)
{
Text = "Superior";
}
}
Click the Button to trigger the @onclick event, set the Text value, and print Blazor is Superior! Each event returns an argument. The @onclick event returns a MouseEventArgs argument. See more about event parameter types
Razor components
Expose events across components, using EventCallback. The parent component can assign callback methods to the child’s EventCallback, which is called by the child.
<! [Parameter] public EventCallback<string bb3 onClick {get; set; } void OnBtnClick(MouseEventArgs e) { if (OnClick.HasDelegate) OnClick.InvokeAsync("Superior"); }}
<! > <h1>Blazor is @text! </h1> <Component OnClick="OnClick"></Component> @code { private string Text { get; set; } void OnClick(string e) { Text = e; }}
EventCallback<string> OnClick
Defines a name calledOnClick
The event,EventCallback
The generic parameter to is the parameter type of the event.OnClick.InvokeAsync("Superior")
Call this event and let the registered method execute, noting that the event passes before calling itOnClick.HasDelegate
Determine if the event is registered, and if no methods register the event, the call throws an exception.OnClick="OnClick"
将OnClick
Method is registered with the event.
Method 3.
A method exposed by a component that provides external component calls.
<! > <h1>Blazor is @text! </h1> @code { private string Text { get; set; } public void SetText(string text) { Text = text; StateHasChanged(); }}
<! >< Component @ref=" @Component "></Component> <button @onclick=" onclick "></ button> @code {private Component component; void OnClick(MouseEventArgs e) { component.SetText("Superior"); }}
When the Button is clicked and the @onclick event is triggered, the Component’s Text value is set via the Component’s setText method. The Component outputs Blazor is Superior! @Ref wants to get an instance of a Component. It can use the @Ref feature, where it populates the Component variable with an instance of the Component. Note here that the application of @Ref is only completed after the component is rendered.
4. Data binding
Parameter only provides one-way assignment from the external component to the component. Data binding is two-way assignment.
There are some subtle differences in the use of data binding between the original HTML element and the Razor component, which are described below.
The Html element
Data binding functionality is provided using an HTML element feature called @bind.
<h4>Blazor is @Text! </h4> <input @bind="Text" /> @code { private string Text; }
theText
Variables bind toinput
Components, wheninput
Input in and output after leaving focusBlazor is Superior!
.
If we wanted to display the input immediately as we typed it, we could point the binding to the onInput event via the @bind:event property with the event argument.
<h4>Blazor is @Text! </h4> <input @bind="Text" @bind:event="oninput"/> @code { private string Text; }
HTML element binding implementation principle
HTML elements themselves do not support two-way property binding mechanisms when we use@bind
After that, Blazor is generated for usvalue="@Text"
To implement an HTML element assignment, regenerate into@onchange
The event implements the assignment of an HTML element to a binding variable.
<input value="@Text" @onchange="@((ChangeEventArgs __e) => Text = __e.Value.ToString())" /> @code { private string Text { get; set; }}
5. Nested
Component nesting is to allow a component to become a container for another component, through layers of parent and child nesting to achieve a variety of complex interfaces, in this process we can also extract similar components, for repeated use and sharing.
Below is the code for the My Day interface and the nested structure of their components
Child content
A component can set one of its own locations to insert the content of another component.
<! <h1>Blazor is @childContent </h1> @code{[Parameter] public renderFragment childContent {get; set; }}
<! > <Component> <strong>Superior! </strong> </Component>
Component
Has a type ofRenderFragment
的 ChildContent
Properties,RenderFragment
Represents the UI segment to render.ChildContent
Is the UI segment received from the parent component.
You need to render in the componentChildContent
Place the content@ChildContent
The tag.ChildContent
Attributes are named with fixed names. The following example is written in full. The above is abbreviated.
<Component> <ChildContent> <strong>Superior! </strong> </ChildContent> </Component>
The template
You can receive multiple UI segments by specifying one or more component parameters of type RenderFragment.
<! > <h1 >@title is @quality </h1> @code{[Parameter] public renderFragment Title {get; set; } [Parameter] public RenderFragment Quality { get; set; }}
<! > <Component> <Title> <strong>Blazor</strong> </Title> <Quality> Superior! </strong> </Quality> </Component>
Template parameter
You can define a template that supports parameters by defining a component parameter of type RenderFragment< tValue >.
<! >@foreach (var item in Items) {<h4 >@title (item) is Superior! </h4> } @code{ [Parameter] public RenderFragment<string> Title { get; set; } [Parameter] public IReadOnlyList<string> Items { get; set; }}
<! > <Component Items=" Items "> <Title Context="item"> <strong>@item</strong> </Title> </Component> @code{ List<string> items = new List<string> { ".Net", "C#", "Blazor" }; }
Component is passed when usedIReadOnlyList<string> Items
Property passes the content to the component for internal use@foreach (var item in Items)
The collection is rendered in a loop,@Title(item)
The insert location is determined and passed to the templateitem
The value of, and then external throughContext="item"
Receive parameters, and finally achieve the template rendering.
6. Life cycle
The Blazor framework includes both synchronous and asynchronous lifecycle methods. Normally synchronous methods are executed first with asynchronous methods. Lifecycle methods can be overridden to perform other operations on a component during its initialization and rendering.
Component initialization
Component state change
Component destroyed
TODO application componentization
Task information
Important Tasks Whether it’s today or not, we need easy access, so we need to make a “Important Tasks” page. The page displays content that is very similar to “My Day”, so we can abstract a TaskItem. Razor component, and the HTML and style of the component is basically migrated from the TODAY.Razor component.
<Card Bordered="true" Size="small" Class="task-card"> <div class="task-card-item"> @{ var finishClass = new ClassMapper().Add("finish").If("unfinish", () => Item.IsFinish == false); } <div class="@(finishClass.ToString())" @onclick="OnFinishClick"> <Icon Type="check" Theme="outline" /> </div> <div class="title" @onclick="OnCardClick"> @if (TitleTemplate ! = null) { @TitleTemplate } else { <AntDesign.Text Strong> @Item.Title</AntDesign.Text> <br /> <AntDesign.Text Type="@TextElementType.Secondary"> @Item.Description </AntDesign.Text> } </div> <div class="del" @onclick="OnDelClick"> <Icon Type="rest" Theme="outline" /> </div> <div class="date"> @Item.PlanTime.ToShortDateString() <br /> @{ int? days = (int?) Item.Deadline? .Subtract(DateTime.Now.Date).TotalDays; } <span style="color:@(days switch { _ when days > 3 => "#ccc", _ when days > 0 => "#ffd800", _ => "#ff0000" })"> @Item.Deadline? .ToShortDateString() </span> </div> @if (ShowStar) { <div class="star" @onclick="OnStarClick"> <Icon Type="star" Theme="@(Item.IsImportant ? "fill" : "outline")" /> </div> } </div> </Card>
Public partial class TaskItem {// TaskItem [Parameter] public TaskDto Item {get; set; } public EventCallback<TaskDto> onFinish {get; set; } public async void OnFinishClick() { if (OnFinish.HasDelegate) await OnFinish.InvokeAsync(Item); } public EventCallback< taskDto > onCard {get;} public EventCallback< taskDto > onCard; set; } public async void OnCardClick() { if (OnCard.HasDelegate) await OnCard.InvokeAsync(Item); } public EventCallback<TaskDto> onDel {get;} public EventCallback<TaskDto> onDel; set; } public async void OnDelClick() { if (OnDel.HasDelegate) await OnDel.InvokeAsync(Item); } // Parameter [Parameter] public EventCallback<TaskDto> OnStar {get; set; } public async void OnStarClick() { if (OnStar.HasDelegate) await OnStar.InvokeAsync(Item); [Parameter] public bool ShowStar {get; set; } = true; [Parameter] public RenderFragment TitleTemplate {get; set; }}
@if (TitleTemplate ! = null) If an external template is passed in, it is the display template; otherwise, the default format is used for display.
A new task
Both Important Tasks and My Day have the ability to add tasks, and we have abstracted them into the NewTask.Razor component.
</Divider> if (newTask! = null) { <Spin Spinning="isNewLoading"> <div class="task-input"> <DatePicker Picker="@DatePickerType.Date" @bind-Value="@newTask.PlanTime" /> <Input @bind-Value="@newTask.Title" OnkeyUp="OnInsertKey" /> @if(ChildContent! =null ) { @ChildContent(newTask) } </div> </Spin> }
public partial class NewTask { [Inject] public MessageService MsgSrv { get; set; } [Inject] public HttpClient Http { get; set; } [Parameter] public EventCallback<TaskDto> OnInserted { get; set; } [Parameter] public Func<TaskDto> NewTaskFunc { get; set; } [Parameter] public RenderFragment<TaskDto> ChildContent { get; set; } // newTask TaskDto newTask {get; set; } private bool isNewLoading { get; set; } protected override void OnInitialized() { newTask = NewTaskFunc? .Invoke(); base.OnInitialized(); } async void OnInsertKey(KeyboardEventArgs e) { if (e.Code == "Enter") { if (string.IsNullOrWhiteSpace(newTask.Title)) { Msgsrv. Error($" Title must be filled "); return; } isNewLoading = true; var result = await Http.PostAsJsonAsync<TaskDto>($"api/Task/SaveTask", newTask); if (result.IsSuccessStatusCode) { newTask.TaskId = await result.Content.ReadFromJsonAsync<Guid>(); await Task.Delay(1000); if (OnInserted.HasDelegate) await OnInserted.InvokeAsync(newTask); newTask = NewTaskFunc? .Invoke(); } else {msgSrv. Error($" Request Error {result.statusCode}"); } isNewLoading = false; StateHasChanged(); }}}
What needs to be done after an EventCallback< TaskDTO > OnINSERTED may differ between scenarios, so it is handled externally through this event. Func
NewTaskFunc
NewTaskFunc
NewTaskFunc
NewTaskFunc
NewTaskFunc
NewTaskFunc
NewTaskFunc RenderFragment< TaskdTo > childContent uses the template to implement additional forms to extend the input content.
Important task
Create the Star.Razor file as the page file for the important task, as shown below
@Page "/star" <PageHeader Title="@(" Important task ")" Subtitle="@($" Number: {TaskDTOS? .Count}")"></PageHeader> <Spin Spinning="@isLoading"> @foreach (var item in taskDtos) { <TaskItem Item="item" OnFinish="OnFinish" OnCard="OnCardClick" OnDel="OnDel" OnStar="OnStar" ShowStar="false"> </TaskItem> } <NewTask OnInserted="OnInsert" NewTaskFunc="() => new TaskDto() { PlanTime = DateTime.Now.Date, IsImportant = true }"></NewTask> </Spin>
Public partial class Star {// 1, Inject all the work for the day [Inject] public HttpClient HTTP {get; set; } bool isLoading = true; private List<TaskDto> taskDtos = new List<TaskDto>(); protected async override Task OnInitializedAsync() { isLoading = true; taskDtos = await Http.GetFromJsonAsync<List<TaskDto>>("api/Task/GetStarTask"); isLoading = false; await base.OnInitializedAsync(); } //2, public MessageService MsgSrv {get; set; } async void OnInsert(TaskDto item) { taskDtos.Add(item); } //3, edit drawer [Inject] public taskDetailServices taskSrv {get; set; } async void OnCardClick(TaskDto task) { TaskSrv.EditTask(task, taskDtos); await InvokeAsync(StateHasChanged); } private async void OnStar(TaskDto task) {var req = new taskportTanTreq () {tasKid = task.tasKid, IsImportant = ! task.IsImportant, }; var result = await Http.PostAsJsonAsync<SetImportantReq>("api/Task/SetImportant", req); if (result.IsSuccessStatusCode) { task.IsImportant = req.IsImportant; StateHasChanged(); }} / / 5, modify, complete or not private async void OnFinish (TaskDto task) {var the req = new SetFinishReq () {TaskId = task. TaskId, IsFinish = ! task.IsFinish, }; var result = await Http.PostAsJsonAsync<SetFinishReq>("api/Task/SetFinish", req); if (result.IsSuccessStatusCode) { task.IsFinish = req.IsFinish; StateHasChanged(); }} //6, delete [Inject] public ConfirmService ConfirmSrv {get; set; } public async Task onDel (TaskDto Task) {if (await confirmsrv.show ($" {task.title}", "delete ", confirmbutton. YesNo, ConfirmIcon.Info) == ConfirmResult.Yes) { taskDtos.Remove(task); }}}
TaskItem
OnFinish="OnFinish" OnCard="OnCardClick" OnDel="OnDel" OnStar="OnStar"
Bind different operation functions
It’s perfectly possible to extract these methods into a separate service using the services described in the previous section, but I’m going to skip it here.
ShowStar=”false” does not show important ICONS
NewTask NewTaskFunc=”() => new TaskDto() { PlanTime = DateTime.Now.Date, IsImportant = true}” Important initialization sets IsImportant to true by default
My day
We’re going to do the same with My Day
@Page "/today" <PageHeader Title="@(" My day")" Subtitle=" @datetime.now. toString (" YYYYY MM MM DD ")"></PageHeader> <Spin Spinning="@isLoading"> @foreach (var item in taskDtos) { <TaskItem @key="item.TaskId" Item="item" OnFinish="OnFinish" OnCard="OnCardClick" OnDel="OnDel" OnStar="OnStar"> <TitleTemplate> <AntDesign.Text Strong Style="@(item.IsFinish?" text-decoration: line-through; color:silver;" :"")"> @item.Title</AntDesign.Text> <br /> <AntDesign.Text Type="@TextElementType.Secondary"> @item.Description </AntDesign.Text> </TitleTemplate> </TaskItem> } <NewTask OnInserted="OnInsert" NewTaskFunc="()=> new TaskDto() {PlanTime=DateTime.Now.Date }"> <ChildContent Context="newTask"> <RadioGroup @bind-Value="newTask.IsImportant"> <Radio RadioButton Value="true"> </Radio> <Radio RadioButton Value="false"> </Radio> </ childContent > </ newTask > </Spin>
The C# code is not posted here because it changes so little
TaskItem
TitleTemplate
Rewrote the way the title is displayed via the template to support adding a deletion line to the title when it is finished
NewTask ChildContent rewrites the ChildContent to provide a choice of importance.
Time back to the trailer
Of course, only you can see their own to do, so login, permissions what are arranged, please pay attention to the next section – security