Single

在Aspnetcore MVC和Razor Pages项目中使用Blazor

Asp .net core MVC/Razor Pages和Blazor对应着Web的两种大相径庭的风格。

当用户访问网站时,Mvc/Razor Pages技术在服务器上使用模板引擎拼接好每一页HTML后传送给浏览器;而Blazor则是近几年流行的“前后端分离”风格,它在用户首次访问网站时下载好一个相对完整的,跑在浏览器上的App,之后就只与API服务器通过JSON之类的方式交流,用户看到的页面都是由这个App在浏览器本地实时拼接生成,而不由服务器处理页面。

这两种开发方式,在.NET 5之后可以以一些巧妙的方式结合起来,比如一个网站,其中部分页面是MVC方式展示的,另一部分页面是采用前端框架展示的;甚至在同一页页面中,也可以部分内容由MVC方式拼接,部分内容由前端框架处理。

于是,本文我们就来试试看这种两者结合的Web开发怎么玩吧。

Blazor Server

对Blazor有大概了解的话会知道,Blazor主要可以分为两种部署模式:客户端侧和服务端侧.

其中客户端侧模式,就像近几年流行的前后端分离的所谓“大”前端那样,把整个前端工程给加载到浏览器上,然后甚至就可以离线运行了(当然数据交互还是得联网)

而由于要把整个工程加载到浏览器上,这也就有了一个所有前端框架都有的缺点:首次打开网站时要加载的内容体积过大,用户等待时间较长。而与之对应的,Blazor也有另一种服务端侧模式。

服务端侧模式时,用户打开网站时,浏览器只会加载一个最小化的页面,并且与服务器建立起一个WebSocket长连接来处理前端的实时交互。(虽然还是有不少差异,但是我们暂时可以粗略的类比理解为它类似于其他前端框架中提到的SSR-Server Side Rendering技术)

文本我们会先从Blazor Server模式讲起,目标是在Mvc/Razor Pages的网页中嵌入使用Blazor的前端组件,因为这种模式与Asp net core 的MVC/Raozr Pages的工程最为接近,也容易上手。

首先我们来创建一个Blazor Server的空工程.

dotnet new blazorserver -o MeowServer

这个工程乍一看其实是很像Asp .net core Razor Pages的工程的,其实还不仅仅是单纯的像,它们还真是相通的。我们来做个小实验看看,首先在Pages文件夹下新建一个Razor页面Meow.cshtml

然后打开Startup.cs文件,在Configure方法的app.UseEndpoints里加上一行:

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages(); //这一行是我们新加的
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

然后把程序运行起来,浏览器访问到/Meow位置,观察到我们编写的Razor页面已经成功的被运行起来了,这时候,这个项目就变成了一个真正的Razor Pages项目了。

为什么我们要在Blazor Server的项目里纠结Razor Pages呢?因为Blazor Server的运行,还真和Raozr Pages有点关系,我们来看我们修改之前的app.UseEndpoints代码:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

其中第一句代码endpoints.MapBlazorHub() 有的文章里说这玩意是处理Blazor的路由的,其实也不算准确,它是作用是建立WebSocket服务器的,就像我们之前说的用户的实时交互是通过WebSocket传递到服务器运算完了再返回响应到浏览器的,如果我们把这句代码注释掉,我们会发现网页还是正常能打开的,但是点击按钮什么的就没有反应了。

当然WebSocket这个说法其实也不是很准确,它在ASP .NET CORE中具体是指SignalR这种协议,它默认情况下是使用WebSocket进行双工通讯的,当WebSocket在某些场景下不可用时,它会回退到长轮询,SSE等方案进行通讯,但这不是文本的重点。

然后第二句代码,原意是指这句话之前的所有的路由(可能有Controller、Razor Pages等等)都匹配失败的话,会退而访问指定的Page, 也就是/_Host, 也就是我们熟悉的Razor Pages的Pages/_Host.cshtml

其实看到了这儿的代码,我们就已经知道怎么在Razor Pages项目中插入Blazor组件了。关键就两句,首先是

<script src="_framework/blazor.server.js"></script>

这儿加载了一个blazor 服务侧使用的js文件,比如建立websocket (signalr)连接等等工作就是这个js文件提供的,我们暂不细说。

然后就是一句:

<component type="typeof(App)" render-mode="ServerPrerendered" />

这个其实是asp .net core里面的tag helper, 它的作用就是嵌入一个Blazor的组件(Razor组件)到我们的cshtml文件中。

而如果单独使用过Blazor我们会知道,Blazor的一切页面都是组件,而App这个特殊的组件就是Blazor工程中的所有组件的入口,针对前端的路由等等操作都是在这个组件开始的。

也就是说,Blazor Server的工程本身,某种角度就可以理解为是在一个Razor Pages页面中嵌入了Blazor的入口组件而运作起来的。

实战

那么反过来,我们自然也可以在我们现有的Mvc/Razor Pages项目中使用Blazor组件的。来试试吧,首先新建一个Razor Pages (webapp)的空模板工程。

dotnet new webapp -o Meow.Web

然后我们先把Blazor的组件先写出来,首先新建一个目录叫Components (其实叫什么都行),然后在其中新建一个组件MeowComponent.razor , 编写代码如下:

<div class="card card-body mb-3">
    <div>
        <button class="btn btn-primary" @onclick="SayMeow">点我</button>
        <p class="mb-0">Meow: @Count</p>
    </div>
</div>

@code{
    private int Count = 0;
    public void SayMeow()
    {
        Count++;
    }
}

因为我们是打算把这个组件嵌入到Razor Pages页面中的,所以不需要@page的路由标记,然后如果你照着我上面这段代码自己打一遍的话,打到@onclick的时候会觉得特别难受,因为这时候IDE的智能提示没了,和直接在Blazor工程里的状况看起来不太对。

这是怎么回事呢,其实是因为我们差了点东西,我们在刚刚Components这个目录下,再创建一个razor组件,叫_Imports.razor , 然后,我们把下面这些代码粘贴进去:

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop

这个东西就相当于我们Mvc/Razor Pages工程里的_ViewImports.cshtml 差不多是一个作用,并且作用域规则啥的基本也都差不多。

这样一来,我们之前MeowComponent.razor组件里的代码也就都正常了起来。

这儿顺带一提,之前我看到不少英文博客,这里都只有一句@using Microsoft.AspNetCore.Components ,实际上仅仅这句是不够的,我们先别管那么多直接把Blazor空项目模板里的代码复制过来就好了。

Index.cshtml

接下来我们打开Index.cshtml,然后尝试把我们上面写的这个组件,嵌入到这个页面中,代码也不复杂,如下:

@page
@model IndexModel
@using Meow.Web.Components
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<component type="typeof(MeowComponent)" render-mode="Server" />

@section Scripts{ 
    <script src="_framework/blazor.server.js"></script>
}

其中首先using了组件所在的命名空间,然后就和前文一样,一句<script src="_framework/blazor.server.js"></script> , 和一个tag helper <component type="typeof(MeowComponent)" render-mode="ServerPrerendered" />

(然后这个tag helper其实还是可以直接在Razor Pages的模板引擎里传参数给Blazor的组件的,写法是param-xxx,类似于Razor Pages里常见的asp-route-xxx

Startup

然后Index.cshtml改完了之后,我们来关注一下Startup.cs文件,三行代码,让它支持Blazor Server.

首先在ConfigureServices方法里加上一句services.AddServerSideBlazor();

然后在Configure方法的app.UseEndpoints里加上一句:

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
    endpoints.MapBlazorHub(); //这行是新加的
});

新加的这行就是我们之前所的,管服务端侧Blazor组件的交互的。

然后就完事了。

欸不是说三行么,这才加了两行啊,是少了什么吗?少了一行endpoints.MapFallbackToPage("/_Host");

因为我们暂时只是把Blazor组件嵌入到Razor Pages页面中使用,并没有打算直接显示整页的可路由的Blazor组件页面,所以并不需要这行代码。当然,我们以后会说到它的。

这时候我们把项目启动起来,发现这个Blazor组件已经在页面中显示出来了,并且可以正常交互,目标达成。

那么接下来,如果还有下一篇文章的话,我们来说说看,怎么在MVC/Razor Pages使用客户端侧的Blazor组件,会比目前的状况稍微折腾一点点,但折腾完了也是很棒的。

暂无评论

发表评论