Asp.Net Core Cannot access a disposed context instance

Viewed 1198

I'm trying to implement SignalR in order to consume data from a angular frontend application.

I've checked all the results on google that I can find, but I still can't solve my issue.

The error I'm getting is:

Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'AdminContext'

Controller

[Route("api/[controller]")]
[ApiController]
public class ChartController : ControllerBase
{
    private IHubContext<ChartHub> _hub;
    private readonly ILiveMonitoringService _service;

    public ChartController(IHubContext<ChartHub> hub, ILiveMonitoringService service)
    {
        _hub = hub;
        _service = service;

    }
    public IActionResult Get()
    {
        var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", await _service.GetAllAsync()));
        return Ok(new { Message = "Request Completed" });
    }
}

Service

public Task<List<LiveMonitoring>> GetAllAsync()
{
    return _repository.GetAll().Take(100).ToListAsync();
}

Repository

public IQueryable<TEntity> GetAll()
{
    try
    {
        return _adminContext.Set<TEntity>();
    }
    catch (Exception ex)
    {
        throw new Exception("Couldn't retrieve entities");
    }
}

What could be the problem?

1 Answers

I'm pretty sure that TimerManager is your issue. You did not show its declaration but looks like its constructor accepts a callback to be called at some later point of time. And that's the issue. Your scoped service _service is captured in the callback and used at some later point of time when the request has already ended. So after the request ended, the DbContext is disposed and your _service will consume a disposed context.

The fix is to simply get the data first before passing it into your callback so that the _service will not be captured into that callback, like this:

public async Task<IActionResult> Get()
{
    var liveMonitorings = await _service.GetAllAsync();
    var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings));
    return Ok(new { Message = "Request Completed" });
}

We need to change the returned type of Get to Task<IActionResult> to support async call.

If you actually want to call _service.GetAllAsync() at some time later (not at the time of requesting Get) inside the callback, you need to inject an IServiceScopeFactory to create a scope for your service inside that callback, like this:

public IActionResult Get([FromServices] IServiceScopeFactory serviceScopeFactory)
{
    var timerManager = new TimerManager(async () => 
                           {
                             using(var scope = serviceScopeFactory.CreateScope()){
                               var service = scope.ServiceProvider.GetRequiredService<ILiveMonitoringService>();                                   ​
                               ​var liveMonitorings = await service.GetAllAsync();
                               ​return await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings);
                            ​ }
                           ​});
   ​return Ok(new { Message = "Request Completed" });
}

This way you don't need to inject your _service into the controller's constructor (because it's not used at all). ​

Related