Home > Back-end >  Embed JavaScript files inside a .NET class library for Blazor project
Embed JavaScript files inside a .NET class library for Blazor project

Time:05-10

I'm working on a Blazor project and I'm trying to move one of the JavaScript files to a class library, I've read the following guides:

  1. Browser developer tools

    I'm not sure what I'm missing or what I need to do to get this to work so here is the setup I have:

    Facts:

    1. I'm using .NET 6.
    2. I'm using Visual Studio for Mac Version 17.0 Preview (17.0 build 8754).
    3. The name of the solution is LinkScreen.
    4. The name of the application project is Client.Web.
    5. The name of the application assembly is LinkScreen.Client.Web.
    6. The name of the application default namespace is LinkScreen.Client.Web.
    7. The hosting model for the application is Web Assembly.
    8. The name of the class library project is Client.BrowserInterop.
    9. The name of the class library assembly is LinkScreen.Client.BrowserInterop.
    10. The name of the class library default namespace is LinkScreen.Client.BrowserInterop.

    Project Structur and Code

    1. Inside the class library I have a script file called screen-capture.js under the following directory wwwroot\scripts like so:

    enter image description here

    The build action for this is set to Content and copy to output directory is set to Do not copy.

    1. The class library is referenced to the application like so:

    enter image description here

    1. I have a C# class called ScreenCapture that wraps the JavaScript module that is the screen-capture.js and retuns a reference like so:
    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);
        }
    

    Previously I had a reference to the screen-capture.js inside index.html like so <script src="_content/LinkScreen.Client.BrowserInterop/scripts/screen-capture.js" type="module"></script> because I thought it was required to use the IJSRuntime methods and then someone on the Blazor channel enlighten me so I removed the reference but I'm still getting the same 404 error now from the wrapper by calling jsRuntime.InvokeAsync.

    It's important to note that everything works correctly when the script is inside the wwwroot/scripts of application folder.

    1. My program.cs file is the default for .NET 6 Blazor WebAssembly projects and is like so:
    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();
    

    Things I've tried and it still doesn't work:

    1. Clean the project, delete the bin and obj directories.
    2. Clear the browser's cache as well as disabling it.
    3. I've tried both Safari and Chrome but I get the exact same issue.

    CodePudding user response:

    Are you getting a 404 error? try to modify your script source

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

    to

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

    as the offcial document explains:

    The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file. The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if isn't specified in the project file.

    in my case ,it could work afer I modified the source: enter image description here

    enter image description here

    CodePudding user response:

    I was just trying to export my own components to a library and re-discovered the most important Blazor WASM rule :

    • Always clear the Client bin and obj folders.

    What worked after copying the folders from the main project to the class library failed after a couple of executions. It seems the bin folder still contained some scripts so the paths that worked in the main project kept working until a full clean removed them.

    If the script is stored in wwwroot/scripts and you use :

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

    Your methods should be available globally.

    You should consider using JS isolation with side-by-side component scripts to avoid polluting the JS namespace and cluttering your wwwroot folder.

    If your component is names ScreenCapture.razor, create a JS module in the same folder named ScreenCapture.razor.js. You can load this as a module in your component's OnAfterRenderAsync and call its exported methods :

    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);
        }
    

    Example

    I was trying to put the code from this TagSelector component into my own RCL library using JS isolation to keep things tidy. The CSS and JS files are stored in wwwroot directly so normally I'd have to use :

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

    By renaming the files to TagSelector.razor.css and TagSelector.razor.js though, I was able to keep them together with the Razor file. I only need to use the TagSelector component now. The CSS and JS files are imported automatically.

    The JS file was changed to a module :

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

    The module is loaded with :

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

    To call the exported setText method, the following wrapper is used:

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