Aspnetcore: [Blazor server-side] OwningComponentBase disposed inside OnParametersSetAsync during Login/Logout

Created on 8 Apr 2020  路  6Comments  路  Source: dotnet/aspnetcore

Describe the bug

I have custom authentication provider to provide refresh-less login/logout. If I login/logout on page inheriting OwningComponentBase then I get System.ObjectDisposedException

To Reproduce

Not so sure about reproducing steps but mine steps are:
1) Going to page with child component having OwningComponentBase
2) Login/Logout
3) Receive System.ObjectDisposedException

Got Exceptions? Include both the message and the stack trace

blazor.server.js:15 [2020-04-08T17:04:44.371Z] Error: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Category'.
   at Microsoft.AspNetCore.Components.OwningComponentBase`1.get_Service()
   at Shople.Shared.Category.OnParametersSetAsync() in F:\ASP.NET Core\Shople\src\Shared\Category.razor:line 355
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

Component::OnParametersSetAsync (component implement IDisposable)

    protected override async Task OnParametersSetAsync()
    {
        Products = null;

        FilterItems = await Service?.GetCategoryFiltersAsync(Model.Category.Id); // OK, NO EXCEPTION

        StateHasChanged();

        Products = (await Service?.GetCategoryProductsAsync(Model.Category.Id, DisplayOrder, Page)).DiscountList((await AuthenticationStateTask).User); // EXCEPTION, Service is disposed... LINE 355
    }

Further technical details

  • ASP.NET Core version 3.1.2
  • VS 16.5
Answered Resolved area-blazor question

Most helpful comment

@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor

All 6 comments

@glararan thanks for contacting us.

It is likely this is caused by the call to StateHasChanged() after the async work you are doing. You should pass in a cancellation token to your GetCategoryFiltersAsync and to GetCategoryFiltersAsync and trigger cancellation if the component gets disposed.

@javiercn thanks for reply. Could you please provide link to docs how to prevent it? Im not so much familiar with cancellation token.

However wouldnt be simple put Products = await... into condition if Service is not disposed? Shouldnt OnParametersSetAsync be cancelled when OwningComponentBase is disposed?

@glararan
I think from your stack trace the call to StateHasChanged looks to be causing one of your services to be disposed - presumably because its calling Dispose on the component that inherits OwningConponentBase? Maybe try putting in some logging in your Dispose methods to see when this happens. If your OwningComponentBase is being disposed, it will dispose any services that are resolved from its IServiceScope so on the next line where you use some services I assume atleast one of those services was "owned" and was subsequently just disposed. You'd need to detect when your component and services get disposed so that you don't carry on using them. Its not good to keep hold of references to objects outside of their lifetime scope - if OwningComponentBase is your child component and its being disposed / removed from the render tree, then any services that it owns shouldn't be used outside/ above its render tree hierarchy I.e in parent components etc. Not 100% sure whether that is what is happening here but just saying.

Shouldnt OnParametersSetAsync be cancelled when OwningComponentBase is disposed?

If the disposal is happening on another thread, the thread that is half way through executing OnParametersSetAsync won't magically know about this and abort as far as I am aware.. so you'd have to detect this yourself and guard around it. Cancellation tokens are one way to signal between asynchronous operations that is worth looking into with Async being a pretty fundamental part of the stack now.

So I traced it.

Little bit more code about my component
calling
GetCategoryFiltersAsync runs

using (IServiceScope scope = scopeFactory.CreateScope())
{
    DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();

    T item = await DbSet(db).SingleOrDefaultAsync(m => m...., cancellationToken) as T; // FROM THIS POINT Visual Studio jumps into Dispose method of component, NOT service Dispose as the service doesnt implements IDisposable

    if (item is null) // After dispose continuing here
        throw new NullReferenceException("..");

    return .....ToList();
}

Blazor component Dispose

protected override void Dispose(bool disposing)
{
    if (disposing)
        Disposable?.Dispose();
}

From this point Visual Studio returns to Method above to condition

However after I return into component OnParametersSetAsync

FilterItems = await Service?.GetCategoryFiltersAsync(Model.Category.Id, cancellationToken);

if (cancellationToken.IsCancellationRequested) // On this point SERVICE is already disposed
    return;

StateHasChanged();

I am still missing how my GetCategoryFilterAsync jumps into Dispose or rather how to detect Dispose calling to cancel task inside GetCategoryFilterAsync

@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor

@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor

Works like charm! Thank you for example.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rbanks54 picture rbanks54  路  3Comments

ermithun picture ermithun  路  3Comments

FourLeafClover picture FourLeafClover  路  3Comments

aurokk picture aurokk  路  3Comments

fayezmm picture fayezmm  路  3Comments