On the Blazor Server of ASP.NET Core Blazor

Last week, we initially explored the Blazor WebAssembly( On the Blazor WebAssembly of ASP.NET Core Blazor ). This time, let's see how to play Blazor Server.

Blazor Server

There are two kinds of Blazor Technology:

  • Blazor WebAssembly
  • Blazor Server

The Blazor WebAssembly was introduced last time. This time, I'll take a look at the Blazor Server. Blazor Server is a bit like the server-side rendering mode of web assembly. After the server-side rendering, the page is transmitted to the front-end through the SignalR (websocket) technology, and then the dom elements are replaced. In fact, it is not only the rendering of the page, most of the calculation is also done by the server. The Blazor Server mode allows some browsers that do not support WebAssembly to run the blazor project, but the problem is also obvious. The two-way real-time communication based on SignalR puts forward high requirements for the network. Once the number of users is huge, it also brings great challenges to the level expansion of the server. Blazor The user status of the server is maintained in the server, which also causes great pressure on the server memory.
Let's explore what Blazor Server really is with the goal of completing a simple CRUD project. Because the Blazor Webassembly has already talked about the same things, such as data binding, property binding, event binding and so on. See On the Blazor WebAssembly of ASP.NET Core Blazor.

New Blazor Server project

Open vs to find the Blazor Server template, and make sure not to select it as the Blazor Webassembly template.

Look at the generated project structure:

You can see as like as two peas, ASP.Net Core razor pages project structure of Blazor Server. See how Startup is configured:



    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }

There are two main points to note:
In the ConfigureServices method, register the related services of razor:

services.AddServerSideBlazor();

The Blazor related mapping is configured at the endpoint of the Configure method:

endpoints.MapBlazorHub();

Last time, Blazor Webassembly provided our data service through a web API project, this time it was not used. If you need to provide web API services, the blade server itself can host, but the blade server does not need to provide web API services at all, because its data interaction is completed through websocket.

Realize data access

To create a new student class:

  public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public string Class { get; set; }

        public int Age { get; set; }

        public string Sex { get; set; }
    }

Last time we implemented a student repository, we moved here directly:

    public interface IStudentRepository
    {
        List<Student> List();

        Student Get(int id);

        bool Add(Student student);

        bool Update(Student student);

        bool Delete(int id);
    }
}
 public class StudentRepository : IStudentRepository
    {
        private static List<Student> Students = new List<Student> {
                new Student{ Id=1, Name="Small red", Age=10, Class="1 class", Sex="female"},
                new Student{ Id=2, Name="Xiaoming", Age=11, Class="2 class", Sex="male"},
                new Student{ Id=3, Name="cockroach", Age=12, Class="3 class", Sex="male"}
        };

        public bool Add(Student student)
        {
            Students.Add(student);

            return true;
        }

        public bool Delete(int id)
        {
            var stu = Students.FirstOrDefault(s => s.Id == id);
            if (stu != null)
            {
                Students.Remove(stu);
            }

            return true;
        }

        public Student Get(int id)
        {
            return Students.FirstOrDefault(s => s.Id == id);
        }

        public List<Student> List()
        {
            return Students;
        }

        public bool Update(Student student)
        {
            var stu = Students.FirstOrDefault(s => s.Id == student.Id);
            if (stu != null)
            {
                Students.Remove(stu);
            }

            Students.Add(student);
            return true;
        }
    }

Sign up:

 services.AddScoped<IStudentRepository, StudentRepository>();

Implement student list

As last time, delete some content generated by default to reduce interference. I won't say much here. Under the pages folder, create a new student folder and a new List.razor file:

@page "/student/list"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject IStudentRepository Repository

<h1>List</h1>

<p class="text-right">
    <a class="btn btn-primary" href="/student/add">Add</a>
</p>

<table class="table">
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Age</th>
        <th>Sex</th>
        <th>Class</th>
        <th></th>
    </tr>
    @if (_stutdents != null)
    {
        foreach (var item in _stutdents)
        {
            <tr>
                <td>@item.Id</td>
                <td>@item.Name</td>
                <td>@item.Age</td>
                <td>@item.Sex</td>
                <td>@item.Class</td>
                <td>
                    <a class="btn btn-primary" href="/student/modify/@item.Id">modify</a>
                    <a class="btn btn-danger" href="/student/delete/@item.Id">delete</a>
                </td>
            </tr>
        }
    }

</table>

@code {
    private List<Student> _stutdents;

    protected override void OnInitialized()
    {
        _stutdents = Repository.List();
    }
}

This page was copied from the last WebAssembly project, only the next OnInitialized method was changed. Last time, in OnInitialized, you need to get data from the background through httpclient. This time, you don't need to inject httpclient. You just need to inject Repository to get data directly.
Run the following:

F12 take a look at how this page works:


First / student/list is a standard Http GET request. Returned the html of the page. Judging from the returned html code, the bound data has value, which can clearly prove that the Blazor Server technology uses the server-side rendering technology.


_blazor?id=Fv2IGD6CfKpQFZ-fi-e1IQ connection is a websocket long connection, which is used to handle the data interaction between the server and the client.








Implement Edit component

The Edit component is copied directly from the web assembly project without any changes.

@using BlazorServerDemo.Model

<div>
    <div class="form-group">
        <label>Id</label>
        <input @bind="Student.Id" class="form-control" />
    </div>
    <div class="form-group">
        <label>Name</label>
        <input @bind="Student.Name" class="form-control" />
    </div>
    <div class="form-group">
        <label>Age</label>
        <input @bind="Student.Age" class="form-control" />
    </div>
    <div class="form-group">
        <label>Class</label>
        <input @bind="Student.Class" class="form-control" />
    </div>
    <div class="form-group">
        <label>Sex</label>
        <input @bind="Student.Sex" class="form-control" />
    </div>

    <button class="btn btn-primary" @onclick="TrySave">
        //preservation
    </button>

    <CancelBtn Name="cancel"></CancelBtn>
</div>

@code{

    [Parameter]
    public Student Student { get; set; }
    [Parameter]
    public EventCallback<Student> OnSaveCallback { get; set; }

    protected override Task OnInitializedAsync()
    {
        if (Student == null)
        {
            Student = new Student();
        }

        return Task.CompletedTask;
    }

    private void TrySave()
    {
        OnSaveCallback.InvokeAsync(Student);
    }
}

Implement new page

Also, the new page is copied from the last web assembly project, which can reuse a large number of code, only need to change the saved code. Originally, the saved code was submitted to the background through HttpClient. Now, you only need to inject Repository to call Add method.

@page "/student/add"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

<h1>Add</h1>

<Edit Student="Student" OnSaveCallback="OnSave"></Edit>

<div class="text-danger">
    @_errmsg
</div>

@code {

    private Student Student { get; set; }

    private string _errmsg;

    protected override Task OnInitializedAsync()
    {
        Student = new Student()
        {
            Id = 1
        };

        return base.OnInitializedAsync();
    }

    private void OnSave(Student student)
    {
        Student = student;

        var result = Repository.Add(student);

        if (result)
        {
            NavManager.NavigateTo("/student/list");
        }
        else
        {
            _errmsg = "Save failed";
        }
    }

}

We will not talk about binding properties, binding events and other contents here, because they are the same as the web assembly mode. Please refer to the previous article.
Run the following:

Our page is out. Continue to F12 to see how the page is rendered:

It's strange that no Http request has occurred this time, so where is our Add page from? Let's continue to see the message of Websocket:


The client sends a message to the server through websocket, which carries a message: OnLocation Changed“ http://localhost:59470/student/add ", after receiving the message, the server renders the corresponding page html and passes it to the front end through Websocket, and then the front end switches the dom to display the new page. So we don't see any traditional Http request process here.
Click Save to see what happened:


We can see that the client does not send any Http request when clicking save, but sends a message to the background through websocket, which indicates which button has been clicked, and the background will find the method to be executed according to this information. After the method is executed, the front end will be notified to jump the page.
But here's a question. What about the data we fill in? It seems that the data we filled in the text box is not delivered to the background, which is illogical. Think about the possibility that the data will be submitted when the text box is edited. Let's verify:

When we modify the content of the text box and monitor the websocket message, we found that when we finished modifying the focus and left the text box, the data was directly delivered to the server. My software is very powerful. In the past, vue and angularjs implemented the binding technology between front-end html and js objects, while Blazor Server realized the binding technology of front-end and back-end, 666 ah.














Realize editing and deleting pages

It's not much to say that using the above knowledge points can be done easily.
Edit page:

@page "/student/modify/{Id:int}"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

<h1>Modify</h1>

<Edit Student="Student" OnSaveCallback="OnSave"></Edit>

<div class="text-danger">
    @_errmsg
</div>

@code {
    [Parameter]
    public int Id { get; set; }

    private Student Student { get; set; }

    private string _errmsg;

    protected override void OnInitialized()
    {
        Student = Repository.Get(Id);
    }

    private void OnSave(Student student)
    {
        Student = student;

        var result = Repository.Update(student);

        if (result)
        {
            NavManager.NavigateTo("/student/list");
        }
        else
        {
            _errmsg = "Save failed";
        }
    }
}

Delete page:

@page "/student/delete/{Id:int}"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

<h1>Delete</h1>

<h3>
    //Are you sure to delete (@ Student.Id) @ Student.Name?
</h3>

<button class="btn btn-danger" @onclick="OnDeleteAsync">
    //delete
</button>

<CancelBtn Name="cancel"></CancelBtn>

@code {
    [Parameter]
    public int Id { get; set; }

    private Student Student { get; set; }

    protected override void OnInitialized()
    {
        Student = Repository.Get(Id);
    }

    private void OnDeleteAsync()
    {
        var result = Repository.Delete(Id);
        if (result)
        {
            NavManager.NavigateTo("/student/list");
        }
    }
}

Run the following:



summary

The overall development experience of the Blazor Server has been highly consistent with the blazor web assembly model. Although there are two different rendering modes: Webassembly is client-side rendering and Server-side rendering. However, Microsoft uses websocket technology as a layer of agent to hide the differences between them, which makes the development of the two modes keep a high degree of consistency. In addition to the first request to use Http, all other data interactions are completed on the Server side through websocket technology, including page rendering, event processing, data binding, etc., which puts forward great requirements for the network, memory, expansion, etc. of the Blazor Server project. In terms of project selection, it should be carefully considered.

The source code of the final demo: BlazorServerDemo

Tags: network Vue AngularJS

Posted on Fri, 15 May 2020 03:02:42 -0400 by raymie7