网上已经有很多资料,但大多都是只支持一个模式切换,这次实现了Wasm和Server无缝切换,并且去掉了Wasm首次加载时间过长的问题,利用Server先进行访问,等后台资源下载完成后,切换到Wasm模式,减少占用服务器资源和长连接的问题
先看视频,如果不是想要的效果就不用往下看了
Blazor的Wasm和Server模式无缝切换https://www.bilibili.com/video/BV1L3411e7o9/
首先,没有单独写例子,集成到了我的开源软件里,可以查看 Caviar-Blazor,内容有点多,慢慢来分解一下。
有些限制,需要编写的时候按照Wasm进行编码,也就是所有的数据请求需要用Http,不能直接使用对象,然后建立两个项目,一个是server模式,一个是wasm模式,server模式的引用wasm模式就可以,需要把wasm模式里一段代码屏蔽掉,按照自己的来
builder.RootComponents.Add<Caviar.AntDesignUI.App>("#app");
然后就是在server的_Host.cshtml加载两个模型
@page "_Host"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Caviar.AntDesignUI
@using Caviar.Infrastructure
@using Caviar.SharedKernel
@using Microsoft.Extensions.Options
@{
var IsServer = Request.Query.ContainsKey(CurrencyConstant.ServerName);
var host = Request.Host;
var serverHost = $"{Request.Scheme}://{host}?{CurrencyConstant.ServerName}";
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Caviar</title>
<base href="/" />
</head>
<body>
@if(IsServer)
{
<app>
<component type="typeof(Caviar.AntDesignUI.App)" render-mode="ServerPrerendered" />
<persist-component-state />
</app>
<script src="_framework/blazor.server.js"></script>
}
else
{
<div id="iframe_div" style="width:100%; height:100%;border:medium none">
<iframe id="iframe_Server" src='@serverHost' style="width:100%; height:100%;border:medium none"></iframe>
</div>
<app id="wasm_app" style="display: none;">
<component type="typeof(Caviar.AntDesignUI.App)" render-mode="WebAssemblyPrerendered">
</app>
<script src="_framework/blazor.webassembly.js"></script>
}
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" id="uiCss" media="screen">
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
<script src="_content/Caviar.AntDesignUI/js/Caviar.js"></script>
<script src="_content/Caviar.AntDesignUI/js/prism.js"></script>
<link href="_content/Caviar.AntDesignUI/js/prism.css" rel="stylesheet" />
</body>
</html>
当然这里做了一些处理,如果同时加载两个模型,因为会同事订阅事件,会互相处理,所以会卡死,我们直接用iframe进行隔离,我们先显示server模式,把wasm模式隐藏起来,这样就像直接使用srever模式一样。自动切换我们等会再说 ,我们先实现手动切换
现在思路就很清除了,我们可以隐藏iframe也可以直接去掉来节省资源,我们只需要两个js就能搞定
function switch_wasm() {
wasm_app = document.getElementById("wasm_app");
iframe_div = document.getElementById("iframe_div");
wasm_app.style.display = "block";
iframe_div.style.display = "none";
var iframe = document.getElementById("iframe_Server");
iframe.parentNode.removeChild(iframe);
}
function switch_server(url) {
wasm_app = document.getElementById("wasm_app");
wasm_app.style.display = "none";
iframe_div = document.getElementById("iframe_div");
iframe_div.innerHTML = '<iframe id="iframe_Server" src="' + url + '?IsAutomaticSwitchWasm=false&server=true" style="width:100%; height:100%;border:medium none"></iframe>'
iframe_div.style.display = "block"
}
切换wasm模式时,直接移除iframe,然后将wasm显示
切换wasm模式时,重新加载iframe,然后隐藏wasm,非常完美
后面我们需要在wasm加载完毕时,自动切换,那么切换数据的时机很重要,不能在用户操作时候切换,需要在用户切换页面的时候,直接将wasm放出来,用户就感觉不到了。所以我们需要一个事件,当用户切换页面时触发事件,如果加载好了就切换没有加载好就不切换。
这就涉及到了iframe和外面进行通信,好在已经有解决方案
//iframe内发送消息
function iframeMessage(message) {
window.parent.postMessage(message, '*');
}
//iframe外监听
window.addEventListener('message', function (e) {
console.log(e.data);
DotNet.invokeMethod("Caviar.AntDesignUI", "JsNavigation", e.data)
})
iframe只需要调用postMessage,外面就可以监听到,这样通信也有了
//在server模式下且需要自动切换
if (Config.IsServer && UserConfig.IsAutomaticSwitchWasm)
{
var iframeMessage = new IframeMessage();
iframeMessage.Pattern = Pattern.Wasm;
iframeMessage.Url = menuItem.RouterLink;
_ = jSRuntime.InvokeVoidAsync("iframeMessage", iframeMessage);
}
在你需要切换的位置进行调用,当然我们需要判断一下当前是否为server模式,不然在wasm也会调用,还需要判断一下是否需要自动切换,当我们调用js以后,js会调用c#的一个静态方法
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Caviar.AntDesignUI
{
/// <summary>
/// iframe与框架通信类
/// </summary>
public class IframeMessage
{
/// <summary>
/// 调用的模式
/// </summary>
public Pattern Pattern { get; set; }
/// <summary>
/// 转到的地址
/// </summary>
public string Url { get; set; }
/// <summary>
/// 传输的数据
/// </summary>
public object Data { get; set; }
public delegate void JSScheduling(IframeMessage message);
public static event JSScheduling SwitchWasm;
[JSInvokable]
public static void JsNavigation(IframeMessage message)
{
switch (message.Pattern)
{
case Pattern.Wasm:
SwitchWasm?.Invoke(message);
break;
default:
break;
}
}
}
public enum Pattern
{
Wasm,
Server
}
}
在方法里调用事件即可,剩下的就是合适注册事件,所有的组件都是从app开始的,所以app里写再合适不过了
@using Caviar.AntDesignUI.Helper;
@using System.Web
<CascadingAuthenticationState>
<Router @ref="UserConfig.Router" AppAssembly="@typeof(Caviar.AntDesignUI.Config).Assembly"
AdditionalAssemblies="Config.AdditionalAssemblies"
PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeView>
<Authorized>
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(CavMainLayout)" />
@*<ReuseTabsRouteView RouteData="@routeData" DefaultLayout="@typeof(CavMainLayout)" />*@
</Authorized>
<NotAuthorized>
<Caviar.AntDesignUI.Pages.User.Login></Caviar.AntDesignUI.Pages.User.Login>
</NotAuthorized>
</AuthorizeView>
</Found>
<NotFound>
<Caviar.AntDesignUI.Pages.Exception._404._404></Caviar.AntDesignUI.Pages.Exception._404._404>
</NotFound>
</Router>
<AntContainer />
</CascadingAuthenticationState>
@code {
[Inject]
UserConfig UserConfig { get; set; }
[Inject]
NavigationManager NavigationManager { get; set; }
[Inject]
IJSRuntime JSRuntime{ get; set; }
protected override void OnParametersSet()
{
if (!Config.IsServer)
{
//wasm模式初始化完成,接收事件
IframeMessage.SwitchWasm += SwitchWasm_RefChanged;
}
else
{
var uri = new Uri(NavigationManager.Uri);
var collection = HttpUtility.ParseQueryString(uri.Query);
if (!string.IsNullOrEmpty(collection["IsAutomaticSwitchWasm"]))
{
UserConfig.IsAutomaticSwitchWasm = bool.Parse(collection["IsAutomaticSwitchWasm"]);
}
}
base.OnParametersSet();
}
private void SwitchWasm_RefChanged(IframeMessage message)
{
NavigationManager.NavigateTo(message.Url);
JSRuntime.InvokeVoidAsync("switch_wasm");
}
}
在OnParametersSet时判断一下是否为wasm模式,如果是就注册事件既可,事件里进行wasm跳转,并且切换到wasm模式。
当然,在url里需要表明当前是否需要自动切换,不然就不能使用server模式了。