Attack Blazor! This is an introduction to Blazor zero Basics video series that I worked with Teacher Zhang Shanyu to help a programmer who has never worked with Blazor learn how to develop Blazor applications. Video address: space.bilibili.com/483888821/c… This series of articles is based on attack! Blazor! Write and upgrade live content. Net5, improve the problem, explain more comprehensive. Because space is limited, part of the code is omitted in this article, the full sample code: github.com/TimChen44/B…

Chen Chao-chao is a contributor to Ant Design Blazor. He has more than 10 years of experience in the industry. Net technology stack architecture and product development work, now working in Chint Group. Email: [email protected] Welcome readers have any questions to contact me, we make progress together.

In this post, we’re going to talk about the essence of Blazor and what I personally consider to be the best feature of the Blazor framework — the components.

component

A Component is a simple encapsulation of data and methods. Almost all UI-related frameworks have the concept of components (controls).

The early Delphi Component was called VCL (Visual Component Library), which used its own nested way to compose the required user interface, and provided properties, methods, and events to interact with the outside of the Component, with its own independent life cycle, when necessary to destroy.

Later.Net WinForms and WPF components are different from Delphi in design and implementation, but the definition and purpose of the components are almost the same.

Now that the concept of components has been adopted in Angular, the Web front-end framework, the overall concept remains similar.

Some frameworks, such as Delphi and WinForms, divide components into those that are invisible and those that are visible, depending on whether they are visible

Looking at the component design of these frameworks, it can be distilled that the components contain 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, or form. The component contains HTML tags and processing logic needed to insert data or respond to UI events. The components are very flexible and lightweight. It can be nested, reused, and shared between projects.

1. Parameters (properties)

Provides a way to pass data from outside the component to inside the component.

In Blazor, component properties are called parameters. A Parameter is itself a Property, but to make the Blazor framework distinguish between the two, we add a Parameter attribute to the Property to declare the Property as a Parameter to the component.

[Parameter]
public string Text { get; set; }
Copy the code

Component parameters

Component parameters can receive values given in razor pages and can support both simple and complex types.

<! -- component code -->
<h1>Blazor is @Text!</h1>@code { [Parameter] public string Text { get; set; }}Copy the code
<! -- Component use -->
<Component Title="Superior">
Copy the code

If you pass the Superior argument to the component, the component will print Blazor is Superior!

Routing parameters

Components 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 code -->
@page "/RouteParameter/{text}"
<h1>Blazor is @Text!</h1>@code { [Parameter] public string Text { get; set; }}Copy the code

When the /RouteParameter/Superior address is used for routing, the page in the previous example is redirected to and the page outputs Blazor is Superior!

Cascade parameters

In some cases, it is not convenient to use component parameters to flow data from the ancestor component to the child component, 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.

Ancestor components use CascadingValue to set cascaded values to be passed down, and child components use the [CascadingParameter] feature to declare cascaded parameters to receive cascaded values.

A detailed Demo will cover this feature later in this article, but I won’t expand it here.

2. The event

An event is a mechanism that is initiated internally by a component and processed externally.

There are some subtle differences in the use of events between the original Html elements and the Razor component, which are covered below.

The Html element

Events on HTML elements are handled in the @on{EVENT} format (such as @onclick), and the Razor component treats the value of this property as the EVENT handler.

<h1>Blazor is @Text!</h1>
<button @onclick="OnClick">Button</button>@code { private string Text { get; set; } void OnClick(MouseEventArgs e) { Text = "Superior"; }}Copy the code

Clicking the Button triggers the @onclick event, then sets the value of Text, and the component prints Blazor is Superior! Each event returns an argument. The @onclick event returns a MouseEventArgs argument. See more about event parameter types

Razor components

To expose events across components, you can use EventCallback. The parent component can assign a callback method to the child component’s EventCallback, which is called by the child component.

<! -- subcomponent -->
<button @onclick="OnBtnClick">Button</button>
@code {
    [Parameter]
    public EventCallback<string>OnClick { get; set; } void OnBtnClick(MouseEventArgs e) { if (OnClick.HasDelegate) OnClick.InvokeAsync("Superior"); }}Copy the code
<! -- parent component -->
<h1>Blazor is @Text!</h1>
<Component OnClick="OnClick"></Component>@code { private string Text { get; set; } void OnClick(string e) { Text = e; }}Copy the code

EventCallback

OnClick defines an event named OnClick. The generic parameters of EventCallback are the parameter types of the event. Onclick.invokeasync (“Superior”) invokes the event and has the registered method execute. Note that the onclick.hasdelegate checks whether the event has been registered before the invocation. If no method has registered the event, the invocation will raise an exception. OnClick=”OnClick” registers the OnClick method with the event.

Method 3.

Methods that are exposed by a component to provide calls from an external component.

<! -- component code -->
<h1>Blazor is @Text!</h1>@code { private string Text { get; set; } public void SetText(string text) { Text = text; StateHasChanged(); }}Copy the code
<! -- Component use -->
<Component @ref="@component"></Component>
<button @onclick="OnClick">Button</button>@code { private Component component; void OnClick(MouseEventArgs e) { component.SetText("Superior"); }}Copy the code

When clicking the Button triggers the @onclick event, the Component outputs Blazor is Superior! By setting the Component’s Text value via its SetText method. @ref wants to get an instance of a Component by using the @ref property, where it populates the Component variable with instances of Component. Note here that the application of @ref is completed only after the component is rendered.

4. Data binding

The parameter provides only 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 elements and the Razor component, which are covered below.

The Html element

Data binding is provided using an Html element feature named @bind.

<h4>Blazor is @Text!</h4>
<input @bind="Text" />
@code
{
    private string Text;
}
Copy the code

Bind the Text variable to the input component and output Blazor is Superior! When the input is finished and out of focus. .

If we want to display the input immediately as it is entered, we can point the binding to the onInput event with the @bind:event attribute with the event parameter.

<h4>Blazor is @Text!</h4>
<input @bind="Text" @bind:event="oninput"/>
@code
{
    private string Text;
}
Copy the code

Html elements do not support bidirectional attribute binding. When we use @bind, we generate value=” @text “to assign a value to an Html element, and generate @onchange event to assign a value to a bound variable.

<input value="@Text"
    @onchange="@((ChangeEventArgs __e) => Text = __e.Value.ToString())" />@code { private string Text { get; set; }}Copy the code

5. Nested

Component nesting is to allow one component to become a container for another component, through the layers of parent and child nesting to achieve a variety of complex interfaces, in the process we can also extract similar components, reuse and share.

Below is the code for the My Day interface and the nested structure of their components

Child content

A component can set its own location to insert content from another component.

<! -- component code -->
<h1>Blazor is @ChildContent</h1>@code{ [Parameter] public RenderFragment ChildContent { get; set; }}Copy the code
<! -- Component use -->
<Component>
    <strong>Superior!</strong>
</Component>
Copy the code

Component has a ChildContent property of type RenderFragment, which represents the UI segment to be rendered. The value of ChildContent is the UI segment received from the parent component. Place the @ChildContent tag in the component where the ChildContent content needs to be rendered. The ChildContent attribute is named with a fixed name. The following example is the full version, and the above is a short version.

<Component>
    <ChildContent>
        <strong>Superior!</strong>
    </ChildContent>
</Component>
Copy the code

The template

You can receive multiple UI segments by specifying one or more component parameters of type RenderFragment.

<! -- component code -->
<h1>@Title is @Quality</h1>@code{ [Parameter] public RenderFragment Title { get; set; } [Parameter] public RenderFragment Quality { get; set; }}Copy the code
<! -- Component use -->
<Component>
    <Title>
        <strong>Blazor</strong>
    </Title>
    <Quality>
        <strong>Superior!</strong>
    </Quality>
</Component>
Copy the code

Template parameter

Component parameters of type RenderFragment

can be defined to define templates that support the parameters.

<! -- component code -->
@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; }}Copy the code
<! -- Component use -->
<Component Items="items">
    <Title Context="item">
        <strong>@item</strong>
    </Title>
</Component>
@code{
    List<string> items = new List<string> { ".Net", "C#", "Blazor" };
}
Copy the code

The component uses the IReadOnlyList

Items property to pass the contents into the component. The component internally uses @foreach (var item in Items) to loop through the collection. @title (item) determines the insertion position and passes the value of the item to the template. Then the external Context=”item” to receive parameters, the final implementation of the template rendering.

6. Life cycle

The Blazor framework includes both synchronous and asynchronous lifecycle methods. In general, synchronous methods are executed first with asynchronous methods. We can override the lifecycle method to perform additional operations on the component during its initialization and rendering.

Component initialization

Component state changes

Component destroyed

ToDo application component transformation

Task information

Whether it’s today or not, we need easy access, so we need to make a “important Tasks” page. This page displays content very similar to “My Day”, so we can abstract a taskitem.Razor component. The Html and style of the component are 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>
Copy the code
public partial class TaskItem
{
    // Task content
    [Parameter] public TaskDto Item { get; set; }

    // Finish the icon event
    [Parameter] public EventCallback<TaskDto> OnFinish { get; set; }
    public async void OnFinishClick()
    {
        if (OnFinish.HasDelegate)
            await OnFinish.InvokeAsync(Item);
    }

    // Item click event
    [Parameter] public EventCallback<TaskDto> OnCard { get; set; }
    public async void OnCardClick()
    {
        if (OnCard.HasDelegate)
            await OnCard.InvokeAsync(Item);
    }

    // Delete the icon event
    [Parameter] public EventCallback<TaskDto> OnDel { get; set; }
    public async void OnDelClick()
    {
        if (OnDel.HasDelegate)
            await OnDel.InvokeAsync(Item);
    }

    // Important icon events
    [Parameter] public EventCallback<TaskDto> OnStar { get; set; }
    public async void OnStarClick()
    {
        if (OnStar.HasDelegate)
            await OnStar.InvokeAsync(Item);
    }

    // Is similar to important ICONS
    [Parameter] public bool ShowStar { get; set; } = true;

    // Supports header templates
    [Parameter] public RenderFragment TitleTemplate { get; set; }}Copy the code

@if (TitleTemplate ! = null) if the template is passed in, the template is displayed, otherwise the default format is used.

A new task

In both “Important Tasks” and “My Day” there is the ability to add tasks, and we also abstracted them into the Newtask.Razor component.

<Divider Text="New mission"></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>
}
Copy the code
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; }

    // New task
    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 in");
                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) awaitOnInserted.InvokeAsync(newTask); newTask = NewTaskFunc? .Invoke(); }else
            {
                MsgSrv.Error($" request error{result.StatusCode}");
            }
            isNewLoading = false; StateHasChanged(); }}}Copy the code

EventCallback

OnInserted what needs to be done after an insert may be different in different scenarios, so it is handled externally through this event. Func

NewTaskFunc TaskDto initialization requirements vary from scenario to scenario, so use this function to call the initialization. RenderFragment

ChildContent uses templates to implement additional forms to extend the input.

Important task

Create the star.razor file as the page file for the important task with the following code

@page "/star"

<PageHeader Title="@"Important task ")"Subtitle="@ ($"Quantity: {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>
Copy the code
public partial class Star
{
    // 1. List all the work to be done that 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. Add agent
    public MessageService MsgSrv { get; set; }
    async void OnInsert(TaskDto item)
    {
        taskDtos.Add(item);
    }

    //3
    [Inject] public TaskDetailServices TaskSrv { get; set; }
    async void OnCardClick(TaskDto task)
    {
        TaskSrv.EditTask(task, taskDtos);
        await InvokeAsync(StateHasChanged);
    }

    //4
    private async void OnStar(TaskDto task)
    {
        var req = newSetImportantReq() { 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. Whether the modification is complete
    private async void OnFinish(TaskDto task)
    {
        var req = newSetFinishReq() { 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 agent
    [Inject] public ConfirmService ConfirmSrv { get; set; }

    public async Task OnDel(TaskDto task)
    {
        if (await ConfirmSrv.Show($" Whether to delete the task{task.Title}"."Delete", ConfirmButtons.YesNo, ConfirmIcon.Info) == ConfirmResult.Yes) { taskDtos.Remove(task); }}}Copy the code

TaskItem OnFinish="OnFinish" OnCard="OnCardClick" OnDel="OnDel" OnStar="OnStar"Bind different action functions

It’s perfectly possible to extract these methods into a separate service using the service described in the previous section, and I’ll be lazy here.

ShowStar=”false” Does not display important ICONS

NewTask NewTaskFunc=”() => new TaskDto() { PlanTime = DateTime.Now.Date, IsImportant = true}” IsImportant is set to true by default

My day

Let’s take my day and give it a makeover

@page "/today"

<PageHeader Title="@"My Day ")"Subtitle="@DateTime.Now.ToString("yyyyyearsMMmonthdd"Day") ></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">important</Radio>
                <Radio RadioButton Value="false">ordinary</Radio>
            </RadioGroup>
        </ChildContent>
    </NewTask>
</Spin>
Copy the code

The C# code will not be posted here because the changes are small

TaskItem TitleTemplateRewrote the way the title is displayed through the template to support adding a deletion line when the title is finished

NewTask ChildContent overrides the ChildContent to provide a choice of importance.

Time back to the trailer

Of course, only their own to see the backlog, so login, permissions what are arranged on, please pay attention to the next section – security

Learning materials

Learn more about Blazor:aka.ms/LearnBlazor