1

I have a Blazor app built on .NET Core 3.1 and I need to be able to access USB port resources. I keep getting the error:

JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

I have a pretty simple Blazor component wrapping the Blazor.Extensions.WebUSB library

public partial class Recordings : ComponentBase
{
    [Inject] private IUSB _usb { get; set; }
    [Inject] private ILogger<Recordings> _logger { get; set; }
    [Inject] private IJSRuntime _runtime { get; set; }

    private bool _initialized = false;


    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (!_initialized)
        {
            this._usb.OnConnect += OnConnect;
            this._usb.OnDisconnect += OnDisconnect;
            this._usb.Initialize();
            this._initialized = true;
        }
        return Task.CompletedTask;

    }

    protected async Task GetDevices()
    {
        var devices = await this._usb.GetDevices();

        if (devices != null && devices.Length > 0)
        {
            _logger.LogInformation("Device list received");
        }
    }

    private void OnConnect(USBDevice device)
    {
        this._logger.LogInformation("Device connected");
    }

    private void OnDisconnect(USBDevice device)
    {
        this._logger.LogInformation("Device disconnected");
    }
}

And even though I'm doing the JS interop in the OnAfterRenderAsync as suggested I still get the same error. I've tried delaying the call to _usb.Initialize until a button is pressed (meaning the component should definitely have finished rendering.

I've tried disabling prerendering by setting the render-mode attribute in _Host.cshtml to Server instead of ServerPrerendered but nothing changed.

HeineSkov
  • 449
  • 1
  • 7
  • 18

1 Answers1

2

Your code should be like this:

 protected override Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        this._usb.OnConnect += OnConnect;
        this._usb.OnDisconnect += OnDisconnect;
        this._usb.Initialize();
        this._initialized = true;
    }
    return Task.CompletedTask;

}

Note: When the firstRender variable is true, which occurs only once, you can use JSInterop. Before that you can't. This is the right time and place to initialize your JavaScript objects.

The OnAfterRender(Boolean) and OnAfterRenderAsync(Boolean) lifecycle methods are useful for performing interop, or interacting with values recieved from @ref. Use the firstRender parameter to ensure that initialization work is only performed once.

Hope this helps...

enet
  • 41,195
  • 5
  • 76
  • 113
  • Actually that's just what I'm doing. Now I see new error: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Blazor.Extensions.WebUSB.IUSB Lifetime: Singleton ImplementationType: Blazor.Extensions.WebUSB.USB': Cannot consume scoped service 'Microsoft.JSInterop.IJSRuntime' from singleton 'Blazor.Extensions.WebUSB.IUSB'.)' – HeineSkov Feb 13 '20 at 20:29
  • This new issue is not related to the current question described in the question title. However, there is an issue with the scope of your services. The scope of IJSRuntime is 'scoped', whereas the scope of the IUSB is 'singleton.' The only solution is to adjust the IUSB scope to that of IJSRuntime service. Do something like this in Startup.ConfigureServices: services.AddScoped(); instead of services.AddSingleton(); Would you mind marking my answer as accepted, if it solved your problem, so others know it was useful. – enet Feb 13 '20 at 20:57