上周初步对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的更多相关文章

  1. ASP.NET Core Blazor 初探之 Blazor WebAssembly

    最近Blazor热度很高,传说马上就要发布正式版了,做为微软脑残粉,赶紧也来凑个热闹,学习一下. Blazor Blazor是微软在ASP.NET Core框架下开发的一种全新的Web开发框架.Bla ...

  2. ASP.NET CORE网站部署到 windows server 的IIS 上去

    章基于我自己经验的一个总结,在windows服务器上部署asp.net core网站.环境是 windows server 2012数据中心版本 第一步先安装 IIS 服务器 接下来就是一路下一步,然 ...

  3. ASP.NET Core多平台部署 (Windows Server+IIS与CentOS 7+Nginx)

    一,Windows Server+IIS部署 1,安装配置IIS,这个应该都不用多说了,教程一堆 2,下载安装.NET Core Runtime 与 .NET Core SDK,下载请点击下载地址,如 ...

  4. ASP.NET Core网站初探

    原文地址:https://blog.csdn.net/iml6yu/article/details/74530679 目录结构如下图   目录: Properties:属性,记录了项目属性的配置文件. ...

  5. [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件

    前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...

  6. ASP.NET Core Blazor Webassembly 之 组件

    关于组件 现在前端几大轮子全面组件化.组件让我们可以对常用的功能进行封装,以便复用.组件这东西对于搞.NET的同学其实并不陌生,以前ASP.NET WebForm的用户控件其实也是一种组件.它封装ht ...

  7. ASP.NET Core Blazor Webassembly 之 路由

    web最精妙的设计就是通过url把多个页面串联起来,并且可以互相跳转.我们开发系统的时候总是需要使用路由来实现页面间的跳转.传统的web开发主要是使用a标签或者是服务端redirect来跳转.那今天来 ...

  8. [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发

    通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...

  9. 初探ASP.NET Core 3.x (3) - Web的运作流程和ASP.NET Core的运作结构

    本文地址:https://www.cnblogs.com/oberon-zjt0806/p/12215717.html 注意:本篇大量地使用了mermaid绘制图表,加载需要较长的时间,请见谅 [TO ...

随机推荐

  1. Volatile的应用DCL单例模式(四)

    Volatile的应用 单例模式DCL代码 首先回顾一下,单线程下的单例模式代码 /** * 单例模式 * * @author xiaocheng * @date 2020/4/22 9:19 */ ...

  2. 浅析CAS与AtomicInteger原子类

    一:CAS简介 CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,比如我们的atomic包下的原子类就是基于CAS来实现.区别于悲观锁synchroniz ...

  3. Linux-Deepin 下开启SSH远程登陆

    #### 关于deepin系统安装ssh后,root超级用户登录报错的完美解决方案! 最近刚刚接触到deepin,觉得,wow,除了mac,还有这么好看的非win系统,而且第测出那个Linux,宽容度 ...

  4. WEBMIN(CVE-2019-15107) 学习

    简单介绍: Webmin是目前功能最强大的基于Web的Unix系统管理工具.管理员通过浏览器访问Webmin的各种管理功能并完成相应的管理动作.目前Webmin支持绝大多数的Unix系统,这些系统除了 ...

  5. 架构设计 | 分布式业务系统中,全局ID生成策略

    本文源码:GitHub·点这里 || GitEE·点这里 一.全局ID简介 在实际的开发中,几乎所有的业务场景产生的数据,都需要一个唯一ID作为核心标识,用来流程化管理.比如常见的: 订单:order ...

  6. gin请求数据校验

    前言 最近优化gin+vue的前后端分离项目代码时候,发现代码中对请求数据的校验比较繁琐,于是想办法简化它.最终我发现了go-playground/validator开源库很好用. 优化前代码 代码如 ...

  7. python学习笔记(六)---文件操作与异常处理机制

    文件读取 读取整个文件 要读取文件,需要一个包含几行文本的文件.下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行: pi_digits.txt 3.14159 ...

  8. 关于foreach总是报错invalid param等问题

    原因为被foreach的数组可能为空,则会报错,只需做好容错即可,例如 if ( !empty( $arr ) ) { foreach ( $arr as $k => $v ) { } }

  9. Maven Wrapper简介

    文章目录 简介 Maven Wrapper的结构 下载Maven Wrapper 使用 Maven Wrapper简介 简介 开发java项目少不了要用到maven或者gradle,对比gradle而 ...

  10. Java本地的项目,怎么可以让别人通过外网访问-内网穿透

    2019独角兽企业重金招聘Python工程师标准>>> 一.点击链接 https://natapp.cn/ 注册个免费的账户 NATAPP官网 二.登陆进去以后查看authtoken ...