Aspnetcore: DotNetObjectRef.Create raises an exception when called from lifecycle events in preview6

Created on 13 Jun 2019  路  13Comments  路  Source: dotnet/aspnetcore

In preview6, there seems to be a regression in creating DotnetObjectRefs, such that when object refs are created during application lifecycle events, an InvalidOperationException is thrown, complaining that "JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef.".

This happens even if I make sure prerendering has completed and ComponentContext.IsConnected is true.

If I modify app.razor from the default template to this:

@inject IComponentContext ComponentContext
@inject IJSRuntime JSRuntime

<CascadingAuthenticationState>
    <Router AppAssembly="typeof(Startup).Assembly">
        <NotFoundContent>
            <p>Sorry, there's nothing at this address.</p>
        </NotFoundContent>
    </Router>
</CascadingAuthenticationState>

<button @onclick="@InvokeJs">Invoke JS</button>

@code { 
protected override async Task OnAfterRenderAsync()
{
    if (ComponentContext.IsConnected)
    {
        await JSRuntime.InvokeAsync<object>("interopTest.log", DotNetObjectRef.Create(this));
    }
}

protected async Task InvokeJs()
{
    await JSRuntime.InvokeAsync<object>("interopTest.log", DotNetObjectRef.Create(this));
}
}

An exception is thrown during the DotNetObjectRef.Create call in OnAfterRenderAsync. However, if I disable that theOnAfterRenderAsync lifecycle event, and instead click on the button, the DotNetObjectRef.Create call succeeds as expected.

I need to register callbacks into dotnet-land at application startup (mediaQueryListener and similar), this issue breaks my use case.

Duplicate area-blazor bug component ecosystem

Most helpful comment

For those looking to make SamProf's solution a little more portable, here is what I figured out:

Define this helper class somewhere:
```c#
public class Issue11159
{
private IJSRuntime Js { get; set; }

public Issue11159(IJSRuntime jsRuntime)
{
    Js = jsRuntime;
}

private static object CreateDotNetObjectRefSyncObj = new object();

public DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
{
    lock (CreateDotNetObjectRefSyncObj)
    {
        JSRuntime.SetCurrentJSRuntime(Js);
        return DotNetObjectRef.Create(value);
    }
}

public void DisposeDotNetObjectRef<T>(DotNetObjectRef<T> value) where T : class
{
    if (value != null)
    {
        lock (CreateDotNetObjectRefSyncObj)
        {
            JSRuntime.SetCurrentJSRuntime(Js);
            value.Dispose();
        }
    }
}

}

In your Razor component, where you want to make a new DotNetObjectRef, you can do this:
```c#

@inject IJSRuntime JSRuntime

...

@functions {
    protected override void OnAfterRender()
    {
        Issue11159 fixer = new Issue11159(JSRuntime);
        OptionInstance instance = new OptionInstance(); // Whatever you want to create an instance of
        DotNetObjectRef<OptionInstance> reference = fixer.CreateDotNetObjectRef(instance);

        JSRuntime.InvokeAsync<object>("initSelectTest", reference);
    }
}

All 13 comments

I also have this problem

This happens in Server Side Blazor. In client-side all is ok.

I fix this using this code, but it's realy not cool.

[Inject]
        protected IJSRuntime Js { get; set; }

        #region Hack to fix https://github.com/aspnet/AspNetCore/issues/11159

        public static object CreateDotNetObjectRefSyncObj = new object();

        protected DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
        {
            lock (CreateDotNetObjectRefSyncObj)
            {
                JSRuntime.SetCurrentJSRuntime(Js);
                return DotNetObjectRef.Create(value);
            }
        }

        protected void DisposeDotNetObjectRef<T>(DotNetObjectRef<T> value) where T : class
        {
            if (value != null)
            {
                lock (CreateDotNetObjectRefSyncObj)
                {
                    JSRuntime.SetCurrentJSRuntime(Js);
                    value.Dispose();
                }
            }
        }

        #endregion

same problem for me server-side blazor

same problem to

Same problem here
@inject IJSRuntime JsRuntime;
var obj = JsRuntime.InvokeAsync<string>( "MySetFocus", DotNetObjectRef.Create(this));

Thanks for contacting us.
@pranavkm can you please look into this? Thanks!

For those looking to make SamProf's solution a little more portable, here is what I figured out:

Define this helper class somewhere:
```c#
public class Issue11159
{
private IJSRuntime Js { get; set; }

public Issue11159(IJSRuntime jsRuntime)
{
    Js = jsRuntime;
}

private static object CreateDotNetObjectRefSyncObj = new object();

public DotNetObjectRef<T> CreateDotNetObjectRef<T>(T value) where T : class
{
    lock (CreateDotNetObjectRefSyncObj)
    {
        JSRuntime.SetCurrentJSRuntime(Js);
        return DotNetObjectRef.Create(value);
    }
}

public void DisposeDotNetObjectRef<T>(DotNetObjectRef<T> value) where T : class
{
    if (value != null)
    {
        lock (CreateDotNetObjectRefSyncObj)
        {
            JSRuntime.SetCurrentJSRuntime(Js);
            value.Dispose();
        }
    }
}

}

In your Razor component, where you want to make a new DotNetObjectRef, you can do this:
```c#

@inject IJSRuntime JSRuntime

...

@functions {
    protected override void OnAfterRender()
    {
        Issue11159 fixer = new Issue11159(JSRuntime);
        OptionInstance instance = new OptionInstance(); // Whatever you want to create an instance of
        DotNetObjectRef<OptionInstance> reference = fixer.CreateDotNetObjectRef(instance);

        JSRuntime.InvokeAsync<object>("initSelectTest", reference);
    }
}

Same issue here, thanks for the workaround.

I have this same issue, but not when I create the a DotNetObjectRef but when the Framework does, so I can't solved used the approach because I have not control over it, the error was produced when ever a javascript function try to call any c# function, even with third party libraries like Blazor.Extensions.SIgnalR.

This error was produced when I try to call and async function before to start the Host in the Main method and use the ContinueWith syntax like below:
```C#
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var httpFactory = host.Services.GetRequiredService();
FlurlHttp.Configure(c =>
{
c.HttpClientFactory = httpFactory;
});
var configurationManager = host.Services.GetRequiredService();
configurationManager.GetIfNeedsAsync().ContinueWith((task) =>
{
if (task.Exception != null)
{
Console.WriteLine(task.Exception.ToString());
}
host.Run();
});

    }

    public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
     BlazorWebAssemblyHost.CreateDefaultBuilder()
         .UseBlazorStartup<Startup>();
}
Note that the app is running in client-side, and I need to call this function in order to load some resources before to start the app in the host.Run(), since Client Side apps does not has a lifecycle 
https://github.com/aspnet/AspNetCore/blob/8ae07917350772b6478b8fc39a16c41d958d5dc8/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs#L18-L20

And due the way the app start:
https://github.com/aspnet/AspNetCore/blob/8ae07917350772b6478b8fc39a16c41d958d5dc8/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostExtensions.cs#L22-L34

And the limitation with async Main on web assembly runtime we can't use await to sync the execution of the task, if we use ConfigureAwait(false).GetAwaiter().GetResult(); or any flavor of it; the task block the execution when performs the HttpRequest by the HttpClient and the app gets int to a dead lock, the browser stock and the window in the web browser do not respond to any command, even close. If we use: 
```C#
   public class Program
    {
        public static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            var httpFactory = host.Services.GetRequiredService<ModernHttpClientFactory>();
            FlurlHttp.Configure(c =>
            {
                c.HttpClientFactory = httpFactory;
            });
            var configurationManager = host.Services.GetRequiredService<UserConfigurationManager>();
            await configurationManager.GetIfNeedsAsync();
            await host.StartAsync();

        }

        public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
         BlazorWebAssemblyHost.CreateDefaultBuilder()
             .UseBlazorStartup<Startup>();
    }

The app also entry in a dead lock, because under the hock the compiler transform the async Main method into :
```C#
[DebuggerStepThrough]
private static void

(string[] args)
{
Program.Main(args).GetAwaiter().GetResult();
}

So we fall in the same issue adobe.

So we cannot make a http request before the app start in the Program.Main method, the work around was to make the request in the App.SetParametersAsync method by overriding it like: 

```C#
    public override async Task SetParametersAsync(ParameterCollection parameters)
    {
        await configurationManager.GetIfNeedsAsync();
        await base.SetParametersAsync(parameters);
    }

I have the same issue, but only when accessing directly a component with its URL. If I start the app and open the website on the landing page and then open the page with the component, it works. If I directly point to the component as first page requested, I see the exception.

This with 3.0 preview 7.

@AlbertoPa The error is still thrown, it just causes the signalr connection to close so that's why your page doesn't get updated.

I've got the same problem, I'm trying to use https://github.com/BlazorExtensions/SignalR for a signalr client but gives the same error when I try to create a new HubConnectionBuilder in the OnAfterRenderAsync.

//Issue11159 fixer = new Issue11159(Js);
        //var reference = fixer.CreateDotNetObjectRef<HubConnection>(connection);

        connection = new HubConnectionBuilder(Js).WithUrl("https://api.dapperdino.co.uk/discordbothub").Build();

        connection.On<TicketReaction>("TicketReaction", async (TicketReaction message) =>
        {
            if (message.TicketId.ToString() == TicketId)
            {
                var newMessage = await ticketDiscordHelper.GetMessageFromChannel($"ticket{message.TicketId}", ulong.Parse(message.DiscordMessage.MessageId));
                reactions.Prepend(newMessage as DSharpPlus.Entities.DiscordMessage);
                StateHasChanged();
            }
        });

Ps I tried the Issue11159 fixer combined with the Hub but this seems to break all other jsinterop functions (or I'm doing something wrong).

Closing as we're tackling this as part of https://github.com/aspnet/AspNetCore/issues/12082

Was this page helpful?
0 / 5 - 0 ratings