上周初步对Blazor WebAssembly进行了初步的探索(ASP.NET Core Blazor 初探之 Blazor WebAssembly)。此次来看看Blazor Server该怎么玩。html
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。
vue
新建Blazor Server项目
打开vs找到Blazor Server模板,看清楚了不要选成Blazor Webassembly模板。
看看生成的项目结构:
能够看到Blazor Server的项目结构跟ASP.Net Core razor pages 项目是如出一辙的。看看Startup是怎么配置的:
ios
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:
git
services.AddServerSideBlazor();
在Configure方法的终结点配置了Blazor相关的映射:angularjs
endpoints.MapBlazorHub();
上次Blazor Webassembly咱们的数据服务是经过一个Webapi项目提供的,此次不用了。若是须要提供webapi服务,Blazor Server自己就能够承载,可是Blazor Server根本不须要提供webapi服务,由于他的数据交互都是经过websocket完成的。github
实现数据访问
新建student类:web
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; } }
注册一下:api
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