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> OnClickDefines a name calledOnClickThe event,EventCallbackThe 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.HasDelegateDetermine if the event is registered, and if no methods register the event, the call throws an exception.

OnClick="OnClick"OnClickMethod 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; }



theTextVariables bind toinputComponents, wheninputInput 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@bindAfter that, Blazor is generated for usvalue="@Text"To implement an HTML element assignment, regenerate into@onchangeThe 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>



ComponentHas a type ofRenderFragmentChildContentProperties,RenderFragmentRepresents the UI segment to render.

ChildContentIs the UI segment received from the parent component.

You need to render in the componentChildContentPlace the content@ChildContentThe tag.

ChildContentAttributes 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> ItemsProperty 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 templateitemThe 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

TitleTemplateRewrote 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

Learning materials

Learn more about Blazor:https://aka.ms/LearnBlazor