Blazor 引用本地程序集时,如何使用 JavaScript Isolation 动态加载模块?
可以继续使用 <Reference> 引用本地 DLL,但需要另外交付 JavaScript 文件,让使用方应用通过 URL 提供这些文件。程序集引用不会自动建立库的 _content/... 静态资源路径。
当前可以保留 DLL 引用,将 MyLib.js 放入使用方项目的 wwwroot,按封装中的路径部署,继续通过 import 动态加载。
为什么通过 NuGet 可以,直接引用 DLL 却失败
JavaScript Isolation 使用标准 JavaScript 模块。下面的调用会让浏览器或 WebView 请求一个 URL:
await javaScriptRuntime.InvokeAsync<IJSObjectReference>(
"import", "./_content/MyCompany.MyLib/MyLib.js");
这个调用不会根据程序集名称查找 DLL,也不会从 DLL 中提取 JavaScript。模块必须作为静态资源,供浏览器或 WebView 访问。Microsoft Learn:JavaScript Isolation
Razor Class Library(RCL)通过构建和打包流程交付静态资源。项目引用或 NuGet 包引用可以将这些资源接入使用方的静态 Web 资源处理流程。普通的 <Reference> 只引用程序集,不会自动引入库项目的 wwwroot 及其资源配置。Microsoft Learn:RCL 静态资源
保留 <Reference> 的解决步骤
1. 继续引用本地程序集
在使用方的 .csproj 中保留类似配置,按实际位置填写 HintPath:
<ItemGroup>
<Reference Include="MyCompany.MyLib">
<HintPath>../vendor/MyCompany.MyLib.dll</HintPath>
</Reference>
</ItemGroup>
2. 将脚本加入使用方的 wwwroot
要保持现有封装中的 URL 不变,可以在使用方项目中创建以下目录:
Consumer/
├── Consumer.csproj
└── wwwroot/
└── _content/
└── MyCompany.MyLib/
└── MyLib.js
将库的 MyLib.js 复制到这里。如果脚本通过相对路径导入其他模块,也要一并复制,并保留相对目录结构。
这里的 _content/MyCompany.MyLib 是手动创建的应用目录,用于匹配现有 URL。创建这个目录并不代表 <Reference> 已接入 RCL 的静态资源机制。
脚本应在构建前放入 wwwroot,由使用方项目纳入构建和发布。只复制到 DLL 所在目录,或仅设置“复制到输出目录”,都不能保证脚本有可访问的 Web 地址。
如果手上只有 DLL,既没有单独交付的 JavaScript 文件,也没有专门实现的嵌入资源提取机制,就需要先取得对应脚本。
3. 使用原来的模块地址加载
现有封装的 Load 方法可以写成:
using Microsoft.JSInterop;
using System.Threading.Tasks;
public async Task Load(IJSRuntime javaScriptRuntime)
{
if (this.MyLib is null)
{
this.MyLib = await javaScriptRuntime.InvokeAsync<IJSObjectReference>(
"import",
"./_content/MyCompany.MyLib/MyLib.js");
}
}
这里传入普通字符串参数,不依赖示例中的集合表达式语法。使用方仍然只需调用 Load,无需直接处理模块地址。
也可以将脚本放在 wwwroot/js/MyLib.js,并将封装中的地址改为 ./js/MyLib.js。动态导入不要求脚本必须位于 _content 下。
4. 保留首次渲染后的初始化
组件仍可在首次渲染后初始化:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await this.javaScriptFunctionality.Load(
this.javaScriptRuntime);
}
}
调用 GetDOM_ElementOffsetCoordinates 等方法前,要确保 Load 已完成。JavaScript 模块也必须导出对应函数,才能通过 IJSObjectReference.InvokeAsync 调用。
组件还应实现 IAsyncDisposable,在销毁时释放封装:
public async ValueTask DisposeAsync()
{
await ((IAsyncDisposable)this.javaScriptFunctionality)
.DisposeAsync();
}
原代码中的 this.MyLibis not null 需要修正为 this.MyLib is not null。如果应用采用服务端交互模式,释放模块时还应按需要处理连接断开产生的 JSDisconnectedException。Microsoft Learn:模块加载与释放
核对实际请求的路径和响应
问题中的两处路径不一致:
封装中:./_content/MyCompany.MyLib/MyLib.js
异常中:https://0.0.0.1/_content/MyCompany/MyLib.js
MyCompany.MyLib 和 MyCompany 是不同目录,点号不会自动变成目录分隔符。如果这个差异并非整理示例时产生,就需要先统一实际部署路径与导入地址。
自动交付的 RCL 资源通常使用以下路径:
_content/{PackageId}/{库中 wwwroot 下的相对路径}
未设置 PackageId 时,默认使用程序集名称。Microsoft Learn:RCL 资源路径
在浏览器或 WebView 的开发者工具中检查模块请求,确认:
- 是否返回
404。 - 响应正文是否为 JavaScript,而不是路由回退返回的 HTML。
- 是否使用了 JavaScript 模块可接受的 MIME 类型。
- 主模块导入的其他模块是否也能正常加载。
Failed to fetch dynamically imported module 不能单独证明文件缺失,还需要结合实际请求检查路径、响应内容和依赖模块。也不要仅凭异常中的 0.0.0.1 就修改主机地址。
.NET MAUI Blazor Hybrid 的注意事项
Blazor Hybrid 同样要求模块能通过 BlazorWebView 的资源路径访问。默认 HostPage 为 wwwroot/index.html 时,可以将脚本放入应用自身的 wwwroot/_content/MyCompany.MyLib/MyLib.js,并确保应用在构建和打包时包含该文件。
如果修改过 HostPage 所在目录,需要重新核对资源的可访问范围。Hybrid 中的路径取决于宿主页配置,不能直接使用桌面磁盘路径。Microsoft Learn:Blazor Hybrid 静态文件
如果以后希望使用者添加一次依赖就能获得 DLL 和脚本,可以将 RCL 打成 NuGet 包,通过本地包源分发,无需发布到公共 NuGet 服务。如果继续直接引用 DLL,就应约定将程序集和配套静态资源一起交付。
备注:内容仅供参考。
