在 Blazor 项目的 .NET class 库中嵌入 JavaScript 文件

Embed JavaScript files inside a .NET class library for Blazor project

我正在开发一个 Blazor 项目,我正在尝试将 JavaScript 文件之一移动到 class 库,我已阅读以下指南:

  1. https://docs.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-6.0&tabs=visual-studio#consume-content-from-a-referenced-rcl-1
  2. https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0#load-a-script-from-an-external-javascript-file-js
  3. https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-6.0#javascript-isolation-in-javascript-modules

我收到以下错误:

我不确定我遗漏了什么或我需要做什么才能让它工作所以这是我的设置:

事实:

  1. 我正在使用 .NET 6
  2. 我正在使用 Visual Studio 作为 Mac Version 17.0 Preview (17.0 build 8754)
  3. 解决方案的名称是 LinkScreen
  4. 应用项目的名称是Client.Web
  5. 应用程序集的名称是LinkScreen.Client.Web
  6. 应用程序默认命名空间的名称是LinkScreen.Client.Web
  7. 应用程序的托管模型是 Web Assembly。
  8. class 库项目的名称是 Client.BrowserInterop
  9. class 库程序集的名称是 LinkScreen.Client.BrowserInterop
  10. class 库默认命名空间的名称是 LinkScreen.Client.BrowserInterop

项目结构和代码

  1. 在 class 库中,我在以下目录 wwwroot\scripts 下有一个名为 screen-capture.js 的脚本文件,如下所示:

为此,构建操作设置为内容,复制到输出目录设置为不复制。

  1. class 库像这样引用到应用程序:

  1. 我有一个名为 ScreenCapture 的 C# class,它包装了 JavaScript 模块,即 screen-capture.js 并像这样返回一个引用:
public static async ValueTask<ScreenCapture> CreateAsync(
        IJSRuntime jsRuntime,
        ElementReference videoTagReference)
    {
        var jsModuleRef = await jsRuntime.InvokeAsync<IJSInProcessObjectReference>("import", "./_content/LinkScreen.Client.BrowserInterop/screen-capture.js").ConfigureAwait(false);

        return new ScreenCapture(jsModuleRef, videoTagReference);
    }

之前我在 index.html 中引用了 screen-capture.js 就像这样 <script src="_content/LinkScreen.Client.BrowserInterop/scripts/screen-capture.js" type="module"></script> 因为我认为需要使用 IJSRuntime 方法然后有人在Blazor 频道启发了我,所以我删除了引用,但我现在仍然通过调用 jsRuntime.InvokeAsync.

从包装器中得到相同的 404 错误

请务必注意,当脚本位于应用程序文件夹的 wwwroot/scripts 中时,一切正常。

  1. 我的 program.cs 文件是 .NET 6 Blazor WebAssembly 项目的默认文件,如下所示:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using LinkScreen.Client.Web;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();

我已经尝试过但仍然无效:

  1. 清理项目,删除 bin 和 obj 目录。
  2. 清除浏览器的缓存并禁用它。
  3. 我已经尝试过 Safari 和 Chrome,但我遇到了完全相同的问题。

您收到 404 错误了吗? 尝试修改你的脚本源

<script src="_content/LinkScreen.Client.BrowserInterop/scripts/screen-capture.js" type="module"></script>

<script src="./_content/Client.BrowserInterop/scripts/screen-capture.js" type="module"></script>

如官方文档所述:

需要当前目录 (./) 的路径段才能为 JS 文件创建正确的静态资源路径。 {PACKAGE ID} 占位符是库的包 ID。 如果未在项目文件中指定,包 ID 默认为项目的程序集名称。

在我的例子中,它可以在我修改源代码后工作:

我只是想将我自己的组件导出到库和 re-discovered 最重要的 Blazor WASM 规则:

  • 始终清除客户端 bin 和 obj 文件夹。

将文件夹从主项目复制到 class 库后,在几次执行后失败了。 bin 文件夹似乎仍然包含一些脚本,因此在主项目中工作的路径一直有效,直到完全清除它们。

如果脚本存储在 wwwroot/scripts 中并且您使用:

<script src="./_content/Client.BrowserInterop/scripts/screen-capture.js

您的方法应该在全球范围内可用。

您应该考虑将 JS 隔离与 side-by-side 组件脚本一起使用,以避免污染 JS 命名空间 使您的 wwwroot 文件夹混乱。

如果您的组件名称为 ScreenCapture.razor,请在名为 ScreenCapture.razor.js 的同一文件夹中创建一个 JS 模块。您可以将其作为模块加载到组件的 OnAfterRenderAsync 中并调用其导出的方法:

private IJSObjectReference? module;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if(module ==null)
    {
        var path = "./_content/Client.BrowserInterop/ScreenCapture.razor.js";
        module = await JS.InvokeAsync<IJSObjectReference>("import", path);
    }
}

    private ValueTask SetTextAsync(string text)
    {
        if (module is null)
        {
            return ValueTask.CompletedTask;
        }
        return module.InvokeVoidAsync("setValue", TextBox, text);
    }

例子

我试图将 this TagSelector component 中的代码放入我自己的 RCL 库中,使用 JS 隔离来保持整洁。 CSS 和 JS 文件直接存储在 wwwroot 中,所以通常我必须使用 :

<link rel="stylesheet" href="_content/MW.Blazor.TagSelector/styles.css" />
<script src="_content/MW.Blazor.TagSelector/interop.js"></script>

不过,通过将文件重命名为 TagSelector.razor.cssTagSelector.razor.js,我能够将它们与 Razor 文件放在一起。我现在只需要使用 TagSelector 组件。 CSS 和 JS 文件是自动导入的。

JS文件已更改为模块:


export function getValue(element) {
    return element.value;
}

export function setValue(element, value) {
    element.value = value;
}

export function blur(element) {
    element.blur();
}

模块加载:

var path = "./_content/MyLibraryName/TagSelector.razor.js";
module = await JS.InvokeAsync<IJSObjectReference>("import", path);

要调用导出的 setText 方法,使用以下包装器:

    private ValueTask SetTextAsync(string text)
    {
        if (module is null)
        {
            return ValueTask.CompletedTask;
        }
        return module.InvokeVoidAsync("setValue", TextBox, text);
    }

您是否尝试过将 builder.WebHost.UseStaticWebAssets(); 添加到您的 Host Net Core 应用的 Program.cs 中?

最终我按照以下步骤解决了这个问题:

  1. 使用以下选项创建了一个新的 Blazor WebAssembly 应用程序:配置 HTTPS,ASP.NET 核心托管和渐进式 Web 应用程序,在 .NET 6 客户端、共享和服务器中生成 3 个不同的项目。
  2. 测试它按原样工作并且服务器为客户端服务。
  3. 创建了一个 Razor Class 库 (RCL) 没有 检查“支持页面和视图!因为它生成了不同的东西。
  4. 将 RCL 引用到服务器项目。
  5. 在调用 RCL 中存在的组件的客户端上创建了一个页面。
  6. 终于成功了!

现在,我认为作为库的组件对我的旧项目不起作用的原因如下:

  • 我注意到服务器不为客户端服务,客户端是设置为启动项目的项目,而不是我创建的新项目。

  • 我在项目中使用了常规 Class 库而不是 RCL,因为我认为除了启动文件之外没有区别,但显然还有更多。