ASP.NET Core Blazor 初探之 Blazor Server

简介:

ASP.NET Core Blazor 初探之 Blazor Server

上周初步对Blazor WebAssembly进行了初步的探索(ASP.NET Core Blazor 初探之 Blazor WebAssembly)。这次来看看Blazor Server该怎么玩。

Blazor Server
Blazor 技术又分两种:

Blazor WebAssembly
Blazor Server
Blazor WebAssembly上次已经介绍过了,这次主要来看看Blazor Server。Blazor Server 有点像WebAssembly的服务端渲染模式。页面在服务器端渲染完成之后,通过SignalR(websocket)技术传输到前端,再替换dom元素。其实不光是页面的渲染,大部分计算也是服务端完成的。Blazor Server模式可以让一些不支持WebAssembly的浏览器可以运行Blazor项目,可是问题也是显而易见的,基于SignalR的双向实时通信给网络提出了很高的要求,一旦用户量巨大,对服务端的水平扩容也带来很大的挑战,Blazor Server的用户状态都维护在服务端,这对服务端内存也造成很大的压力。
我们还是以完成一个简单的CRUD项目为目标来探究一下Blazor Server究竟是什么。因为前面Blazor Webassembly已经讲过了,相同的东西,比如数据绑定,属性绑定,事件绑定等内容就不多说了,请参见ASP.NET Core Blazor 初探之 Blazor WebAssembly。

新建Blazor Server项目
打开vs找到Blazor Server模板,看清楚了不要选成Blazor Webassembly模板。

看看生成的项目结构:

可以看到Blazor Server的项目结构跟ASP.Net Core razor pages 项目是一模一样的。看看Startup是怎么配置的:

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");
        });
    }
}

主要有2个地方要注意:
在ConfigureServices方法里注册了Blazor的相关service:

services.AddServerSideBlazor();
在Configure方法的终结点配置了Blazor相关的映射:

endpoints.MapBlazorHub();
上次Blazor Webassembly我们的数据服务是通过一个Webapi项目提供的,这次不用了。如果需要提供webapi服务,Blazor Server本身就可以承载,但是Blazor Server根本不需要提供webapi服务,因为他的数据交互都是通过websocket完成的。

实现数据访问
新建student类:

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; }
}

上次我们实现了一个StudentRepository,我们直接搬过来:

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="小红", Age=10, Class="1班", Sex="女"},
            new Student{ Id=2, Name="小明", Age=11, Class="2班", Sex="男"},
            new Student{ Id=3, Name="小强", Age=12, Class="3班", Sex="男"}
    };

    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;
    }
}

注册一下:

services.AddScoped();
实现学生列表
跟上次一样,先删除默认生成的一些内容,减少干扰,这里不多说了。在pages文件夹下新建student文件夹,新建List.razor文件:

@page "/student/list"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject IStudentRepository Repository

List

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

Id Name Age Sex Class
@item.Id @item.Name @item.Age @item.Sex @item.Class 修改 删除

@code {

private List<Student> _stutdents;

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

}
这个页面是从上次的WebAssembly项目上复制过来的,只改了下OnInitialized方法。上次OnInitialized里需要通过Httpclient从后台获取数据,这次不需要注入HttpClient了,只要注入Repository就可以直接获取数据。
运行一下:

F12看一下这个页面是如何工作的:

首先/student/list是一次标准的Http GET请求。返回了页面的html。从返回的html代码上来看绑定的数据已经有值了,这可以清楚的证明Blazor Server技术使用的是服务端渲染技术。

_blazor?id=Fv2IGD6CfKpQFZ-fi-e1IQ连接是个websocket长连接,用来处理服务端跟客户端的数据交互。

实现Edit组件
Edit组件直接从Webassembly项目复制过来,不用做任何改动。

@using BlazorServerDemo.Model

<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">
    保存
</button>

<CancelBtn Name="取消"></CancelBtn>

@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);
}

}
实现新增页面
同样新增页面从上次的Webassembly项目复制过来,可以复用大量的代码,只需改改保存的代码。原来保存代码是通过HttpClient提交到后台来完成的,现在只需要注入Repository调用Add方法即可。

@page "/student/add"

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

Add

@_errmsg

@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 = "保存失败";
    }
}

}
这里不再多讲绑定属性,绑定事件等内容,因为跟Webassembly模式是一样的,请参见上一篇。
运行一下 :

我们的页面出来了。继续F12看看页面到底是怎么渲染出来的:

这次很奇怪并没有发生任何Http请求,那么我们的Add页面是哪里来的呢,让我们继续看Websocket的消息:

客户端通过websocket给服务端发了一个消息,里面携带了一个信息:OnLocation Changed "http://localhost:59470/student/add",服务端收到消息后把对应的页面html渲染出来通过Websocket传递到前端,然后前端进行dom的切换,展示新的页面。所以这里看不到任何传统的Http请求的过程。
点一下保存看看发生了什么:

我们可以看到点击保存的时候客户端同样没有发送任何Http请求,而是通过websocket给后台发了一个消息,这个消息表示哪个按钮被点击了,后台会根据这个信息找到需要执行的方法,方法执行完后通知前端进行页面跳转。
但是这里有个问题,我们填写的数据呢?我们在文本框里填写的数据貌似没有传递到后台,这就不符合逻辑了啊。想了下有可能是文本框编辑的时候数据就提交回去了,让我们验证下:

我们一边修改文本框的内容,一边监控websocket的消息,果然发现了,当我们修改完焦点离开文本框的时候,数据直接被传递到了服务器。厉害了我的软,以前vue,angularjs实现的是前端html跟js对象的绑定技术,而Blazor Server这样就实现了前后端的绑定技术,666啊。

实现编辑跟删除页面
这个不多说了使用上面的知识点轻松搞定。
编辑页面:

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

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

Modify

@_errmsg

@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 = "保存失败";
    }
}

}
删除页面:

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

@using BlazorServerDemo.Model
@using BlazorServerDemo.Data

@inject NavigationManager NavManager
@inject IStudentRepository Repository

Delete

确定删除(@Student.Id)@Student.Name ?

删除

@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");
    }
}

}
运行一下:

总结
Blazor Server总体开发体验上跟Blazor Webassembly模式保持了高度一直。虽然是两种不同的渲染模式:Webassembly是客户端渲染,Server模式是服务端渲染。但是微软通过使用websocket技术作为一层代理,巧妙隐藏了两者的差异,让两种模式开发保持了高度的一致性。Blazor Server除了第一次请求使用Http外,其他数据交互全部通过websocket技术在服务端完成,包括页面渲染、事件处理、数据绑定等,这样给Blazor Server项目的网络、内存、扩展等提出了很大的要求,在项目选型上还是要慎重考虑。

最后demo的源码:BlazorServerDemo

Email:kklldog@gmail.com
作者:Agile.Zhou(kklldog)
出处:http://www.cnblogs.com/kklldog/

相关文章
|
15天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
3月前
|
开发框架 前端开发 JavaScript
盘点72个ASP.NET Core源码Net爱好者不容错过
盘点72个ASP.NET Core源码Net爱好者不容错过
71 0
|
3月前
|
开发框架 .NET
ASP.NET Core NET7 增加session的方法
ASP.NET Core NET7 增加session的方法
37 0
|
1月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
C#/.NET/.NET Core拾遗补漏合集(持续更新)
|
1月前
|
Windows
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
114 0
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
63 0
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
33 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
41 0
|
1月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
99 5