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<IStudentRepository, StudentRepository>();
实现学生列表
跟上次一样,先删除默认生成的一些内容,减少干扰,这里不多说了。在pages文件夹下新建student文件夹,新建List.razor文件:
@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">修改</a>
<a class="btn btn-danger" href="/student/delete/@item.Id">删除</a>
</td>
</tr>
}
}
</table>
@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>
<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>
</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);
}
}
实现新增页面
同样新增页面从上次的Webassembly项目复制过来,可以复用大量的代码,只需改改保存的代码。原来保存代码是通过HttpClient提交到后台来完成的,现在只需要注入Repository调用Add方法即可。
@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 = "保存失败";
}
}
}
这里不再多讲绑定属性,绑定事件等内容,因为跟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
<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 = "保存失败";
}
}
}
删除页面:
@page "/student/delete/{Id:int}"
@using BlazorServerDemo.Model
@using BlazorServerDemo.Data
@inject NavigationManager NavManager
@inject IStudentRepository Repository
<h1>Delete</h1>
<h3>
确定删除(@Student.Id)@Student.Name ?
</h3>
<button class="btn btn-danger" @onclick="OnDeleteAsync">
删除
</button>
<CancelBtn Name="取消"></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");
}
}
}
运行一下:


总结
Blazor Server总体开发体验上跟Blazor Webassembly模式保持了高度一直。虽然是两种不同的渲染模式:Webassembly是客户端渲染,Server模式是服务端渲染。但是微软通过使用websocket技术作为一层代理,巧妙隐藏了两者的差异,让两种模式开发保持了高度的一致性。Blazor Server除了第一次请求使用Http外,其他数据交互全部通过websocket技术在服务端完成,包括页面渲染、事件处理、数据绑定等,这样给Blazor Server项目的网络、内存、扩展等提出了很大的要求,在项目选型上还是要慎重考虑。
最后demo的源码:BlazorServerDemo
ASP.NET Core Blazor 初探之 Blazor Server的更多相关文章
- ASP.NET Core Blazor 初探之 Blazor WebAssembly
最近Blazor热度很高,传说马上就要发布正式版了,做为微软脑残粉,赶紧也来凑个热闹,学习一下. Blazor Blazor是微软在ASP.NET Core框架下开发的一种全新的Web开发框架.Bla ...
- ASP.NET CORE网站部署到 windows server 的IIS 上去
章基于我自己经验的一个总结,在windows服务器上部署asp.net core网站.环境是 windows server 2012数据中心版本 第一步先安装 IIS 服务器 接下来就是一路下一步,然 ...
- ASP.NET Core多平台部署 (Windows Server+IIS与CentOS 7+Nginx)
一,Windows Server+IIS部署 1,安装配置IIS,这个应该都不用多说了,教程一堆 2,下载安装.NET Core Runtime 与 .NET Core SDK,下载请点击下载地址,如 ...
- ASP.NET Core网站初探
原文地址:https://blog.csdn.net/iml6yu/article/details/74530679 目录结构如下图 目录: Properties:属性,记录了项目属性的配置文件. ...
- [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件
前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...
- ASP.NET Core Blazor Webassembly 之 组件
关于组件 现在前端几大轮子全面组件化.组件让我们可以对常用的功能进行封装,以便复用.组件这东西对于搞.NET的同学其实并不陌生,以前ASP.NET WebForm的用户控件其实也是一种组件.它封装ht ...
- ASP.NET Core Blazor Webassembly 之 路由
web最精妙的设计就是通过url把多个页面串联起来,并且可以互相跳转.我们开发系统的时候总是需要使用路由来实现页面间的跳转.传统的web开发主要是使用a标签或者是服务端redirect来跳转.那今天来 ...
- [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发
通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...
- 初探ASP.NET Core 3.x (3) - Web的运作流程和ASP.NET Core的运作结构
本文地址:https://www.cnblogs.com/oberon-zjt0806/p/12215717.html 注意:本篇大量地使用了mermaid绘制图表,加载需要较长的时间,请见谅 [TO ...
随机推荐
- 百度关键词搜索工具 v1.1|url采集工具 v1.1
功能介绍:关键词搜索工具 批量关键词自动搜索采集 自动去除垃圾二级泛解析域名 可设置是否保存域名或者url 持续更新中
- python爬虫实例,一小时上手爬取淘宝评论(附代码)
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 1 明确目的 通过访问天猫的网站,先搜索对应的商品,然后爬取它的评论数据. ...
- v&n赛 内存取证题解(已更新)
题目是一个raw的镜像文件 用volatility搜索一下进程 有正常的notepad,msprint,还有dumpit和truecrypt volatility -f mem.raw --profi ...
- [腾讯云]简单在腾讯云 CenTOS7.0 安装Nginx,Mysql(MariaDB),Memcache,解析PHP!
1.安装LNMP之前要安装EPEL,以便安装源以外的软件,如Nginx,phpMyAdmin等. yum install epel-release 2.安装Nginx a) yum install n ...
- python列表简介
什么是列表?如何使用列表?https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range 列表相关知识: ...
- PHP 语法字符串函数 strcmp、strlen 使用及实现
说明 这里基于 php7.2.5 进行测试,php7 之后内部结构变化应该不是太大,但与 php5.X 有差别. 函数分类 用户自定义函数 say(); function say() { echo & ...
- ORM之单表、多表操作
参考1 参考2 表与表之间的关系: 一对一(OneToOneField):一对一字段无论建在哪张关系表里面都可以,但是推荐建在查询频率比较高的那张表里面 一对多(ForeignKey):一对多字段建在 ...
- 2019-2020-1 20199326《Linux内核原理与分析》第九周作业
进程的切换和系统的一般执行过程 中断 中断在本质上都是软件或者硬件发生了某种情形而通知处理器的行为,处理器进而停止正在运行的指令流(当前进程),对这些通知做出相应反应,即转去执行预定义的中断处理程序( ...
- 引入OpenCV导致私有内存巨大
引入OpenCV导致私有内存巨大 opencvC++VS2015 说明 在调试程序的时候 发现自己的程序在VS的调试窗口占用很高, 花时间关注了一下这个问题, 手动写了小的程序复现这个问题,最终确定了 ...
- java.util.concurrent简介
文章目录 主要的组件 Executor ExecutorService ScheduledExecutorService Future CountDownLatch CyclicBarrier Sem ...