Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DotNetObjectRef.Create raises an exception when called from lifecycle events in preview6 #11159

Closed
kvantetore opened this issue Jun 12, 2019 · 13 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. component ecosystem Indicates an issue which also has impact on 3rd party component ecosystem ✔️ Resolution: Duplicate Resolved as a duplicate of another issue

Comments

@kvantetore
Copy link

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.

@Eilon Eilon added the area-blazor Includes: Blazor, Razor Components label Jun 12, 2019
@SamProf
Copy link
Contributor

SamProf commented Jun 13, 2019

I also have this problem

@SamProf
Copy link
Contributor

SamProf commented Jun 13, 2019

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

@SamProf
Copy link
Contributor

SamProf commented Jun 13, 2019

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

SamProf added a commit to SamProf/MatBlazor that referenced this issue Jun 13, 2019
stavroskasidis added a commit to stavroskasidis/BlazorContextMenu that referenced this issue Jun 13, 2019
@Mercurial
Copy link

Mercurial commented Jun 14, 2019

same problem for me server-side blazor

@astecenko
Copy link

same problem to

@ghost
Copy link

ghost commented Jun 18, 2019

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

@mkArtakMSFT
Copy link
Member

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

@mkArtakMSFT mkArtakMSFT added this to the 3.0.0-preview8 milestone Jun 21, 2019
@mkArtakMSFT mkArtakMSFT added bug This issue describes a behavior which is not expected - a bug. and removed investigate labels Jun 21, 2019
@ajruckman
Copy link

ajruckman commented Jul 4, 2019

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

Define this helper class somewhere:

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:

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

@Joe4evr
Copy link

Joe4evr commented Jul 5, 2019

Same issue here, thanks for the workaround.

@danielmeza
Copy link

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:

    public class Program
    {
        public static void 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>();
            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:

   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 :

        [DebuggerStepThrough]
	private static void <Main>(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:

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

@AlbertoPa
Copy link

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.

@DapperMickie
Copy link

DapperMickie commented Aug 2, 2019

@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).

@mkArtakMSFT
Copy link
Member

Closing as we're tackling this as part of #12082

@mkArtakMSFT mkArtakMSFT added the ✔️ Resolution: Duplicate Resolved as a duplicate of another issue label Aug 12, 2019
@danroth27 danroth27 added the component ecosystem Indicates an issue which also has impact on 3rd party component ecosystem label Aug 27, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. component ecosystem Indicates an issue which also has impact on 3rd party component ecosystem ✔️ Resolution: Duplicate Resolved as a duplicate of another issue
Projects
None yet
Development

No branches or pull requests