zoukankan      html  css  js  c++  java
  • 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模板。
    Y0l7r9.md.png
    看看生成的项目结构:
    Y01EPf.png
    可以看到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就可以直接获取数据。
    运行一下:
    YmIGvQ.md.png
    F12看一下这个页面是如何工作的:
    Y0sxKA.md.png
    Y0yyMd.md.png
    首先/student/list是一次标准的Http GET请求。返回了页面的html。从返回的html代码上来看绑定的数据已经有值了,这可以清楚的证明Blazor Server技术使用的是服务端渲染技术。
    Y068Ff.md.png
    Y06aOs.md.png
    _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模式是一样的,请参见上一篇。
    运行一下 :
    YnDgXT.md.png
    我们的页面出来了。继续F12看看页面到底是怎么渲染出来的:
    Y02KOJ.md.png
    这次很奇怪并没有发生任何Http请求,那么我们的Add页面是哪里来的呢,让我们继续看Websocket的消息:
    Y0200A.md.png
    Y02rkt.md.png
    客户端通过websocket给服务端发了一个消息,里面携带了一个信息:OnLocation Changed "http://localhost:59470/student/add",服务端收到消息后把对应的页面html渲染出来通过Websocket传递到前端,然后前端进行dom的切换,展示新的页面。所以这里看不到任何传统的Http请求的过程。
    点一下保存看看发生了什么:
    Y0fJf0.png
    Y0ftpV.png
    我们可以看到点击保存的时候客户端同样没有发送任何Http请求,而是通过websocket给后台发了一个消息,这个消息表示哪个按钮被点击了,后台会根据这个信息找到需要执行的方法,方法执行完后通知前端进行页面跳转。
    但是这里有个问题,我们填写的数据呢?我们在文本框里填写的数据貌似没有传递到后台,这就不符合逻辑了啊。想了下有可能是文本框编辑的时候数据就提交回去了,让我们验证下:
    Y05plV.md.png
    我们一边修改文本框的内容,一边监控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");
            }
        }
    }
    

    运行一下:
    YnTaRK.md.png
    YuZE5R.md.png

    总结

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

    最后demo的源码:BlazorServerDemo

    关注我的公众号一起玩转技术

  • 相关阅读:
    2021.1.28 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2020.12.14 个人训练赛补题报告
    2020.11.28 2020团体程序设计天梯赛补题报告
    2020.12.3 Codeforces Beta Round #73(Div2)补题报告
    Xhorse VVDI Prog V5.0.6 is Ready for BCM2 Adapter
    Program 2021 Ford Bronco All Keys Lost using VVDI Key Tool Plus
    Xhorse VVDI Prog V5.0.4 Software Update in July 2021
    How to use Xhorse VVDI2 to Exchange BMW FEM/BDC Module?
  • 原文地址:https://www.cnblogs.com/kklldog/p/blazor-server.html
Copyright © 2011-2022 走看看