上週初步對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