48

On an ASP.NET Core project I have the following on Startup:

  services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));

  services.AddTransient<IValidationService, ValidationService>();

  services.AddTransient<IValidator<Model>, ModelValidator>();

The ValidationService is as follows:

public interface IValidationService {
    Task<List<Error>> ValidateAsync<T>(T model);
}

public class ValidationService : IValidationService {
    private readonly IServiceProvider _provider;

    public ValidationService(IServiceProvider provider) {
        _provider = provider;
    }

    public async Task<List<Error>> ValidateAsync<T>(T model) {
        IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();

        return await validator.ValidateAsync(model);
    }
}

And the ModelValidator is as follows:

public class ModelValidator : AbstractValidator<Model> {
  public ModelValidator(Context context) {
    // Some code using context
  }
}

When I inject a IValidationService in a controller and use it as:

List<Error> errors = await _validator.ValidateAsync(order);    

I get the error:

System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur is you are calling Dispose() on the context, or wrapping the context 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: 'Context'.

Any idea why I am having this error when using Context inside ModelValidator.

How to fix this?

UPDATE

So I changed the code to:

services.AddScoped<IValidationService, ValidationService>();

services.AddScoped<IValidator<Model>, ModelValidator>();

But I get the same error ...

UPDATE - Seed Data Code inside Configure method on Startup

So on Configure method I have:

if (hostingEnvironment.IsDevelopment())
  applicationBuilder.SeedData();

And the SeedData extension is:

public static class DataSeedExtensions {
    private static IServiceProvider _provider;

    public static void SeedData(this IApplicationBuilder builder) { 
        _provider = builder.ApplicationServices;
        _type = type;

        using (Context context = (Context)_provider.GetService<Context>()) {
            await context.Database.MigrateAsync();
            // Insert data code
    }
}

What am I missing?

UPDATE - A possible solution

Changing my Seed method to the following seems to work:

using (IServiceScope scope = 
    _provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
    Context context = _provider.GetService<Context>();
    // Insert data in database
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • Do you seed your database on startup? If so show your code there pitfalls if you resolve the DbContext during startup (i.e. in Configure method) and use it there to seed the data, w/o creating a scoped Provider before – Tseng Aug 01 '16 at 16:54
  • Wow, when removing the my seed code from Startup I stopped having the problem ... What is strange is that I only get the error when I started to use the ValidationService. What do you suggest to solve this? – Miguel Moura Aug 01 '16 at 16:59
  • I mean, I followed the suggestion of ASP.NET Core for seeding data in Configure ... Any other option? Why this fails when using the ValidationService in particular and only that one? – Miguel Moura Aug 01 '16 at 17:02
  • Post your code, I think you're not creating a scope for the seeding – Tseng Aug 01 '16 at 17:04
  • Try applying this pattern used int he MusicStore sample application https://github.com/aspnet/MusicStore/blob/1.0.0/src/MusicStore/Models/SampleData.cs#L22-L34 Notice that it creates a scope, then resolve the dbcontext and after seeding disposes the scope again. – Tseng Aug 01 '16 at 17:06
  • @Tseng just added my code of seeding data. Am I missing something there? – Miguel Moura Aug 01 '16 at 17:09
  • I've had similar problems, just with .NET Identity Core - see this issue, no solution yet: https://github.com/aspnet/Identity/issues/911 – janhartmann Aug 01 '16 at 17:13
  • 1
    @janhartmann Check my update ... It seems to work. – Miguel Moura Aug 01 '16 at 17:16
  • @Tseng I followed the example in MusicStore and it seems to work ... – Miguel Moura Aug 01 '16 at 17:16
  • Your posted code should be `Context context = scope.GetService();` rather than `Context context = _provider.GetService();`, because you want to resolve it from scope not from the global container which was the case before – Tseng Aug 01 '16 at 17:36

11 Answers11

117

Just a guess in what causes your error:

You are using DI and async calls. If, somewhere in your call stack, you return a void instead of Task, you get the described behavior. At that point, the call is ended and the context disposed. So check if you have an async call that returns a void instead of Task. If you change the return value, the ObjectDisposedException is probably fixed.

public static class DataSeedExtensions {
private static IServiceProvider _provider;

public static async Task SeedData(this IApplicationBuilder builder) { //This line of code

  _provider = builder.ApplicationServices;
  _type = type;

  using (Context context = (Context)_provider.GetService<Context>()) {

    await context.Database.MigrateAsync();
    // Insert data code

  }

}

And in configure:

if (hostingEnvironment.IsDevelopment()){
   await  applicationBuilder.SeedData();
}

Blog post on how to fix this error: Cannot access a disposed object in ASP.NET Core when injecting DbContext

Pang
  • 9,564
  • 146
  • 81
  • 122
Peter
  • 27,590
  • 8
  • 64
  • 84
37

I had a similar issue working with asp.net core. I have an async POST method in my controller and when it returns void I will have this exception. After I changed the POST method return a TASK the problem was solved.

Change from:

public async void PostAsync([FromBody] Model yourmodel)

To

public async Task PostAsync([FromBody] Model yourmodel)
Yang Zhang
  • 4,540
  • 4
  • 37
  • 34
  • Works in my Get method too! – Nilay Mehta Oct 20 '18 at 02:16
  • @Barnsley I think it is because await only works for a `Task`. If a `void` is returned the framework would not handle it internally. – Yang Zhang Jan 31 '20 at 04:40
  • Ya this trick worked! Pathetic why should it be so??? in my case, I forgot the await keyword before `mediatr.send`. Adding the await keyword fixed the error. – blogs4t Aug 28 '22 at 17:04
23

Update for ASP.NET Core 2.1

In ASP.NET Core 2.1 the methods changed slightly. The general method is similar to the 2.0, just the methods name and return types have been changed.

public static void Main(string[] args)
{
    CreateWebHostBuilder(args)
        .Build()
        .Seed();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    return new WebHostBuilder()
        ...; // Do not call .Build() here
}

Applies for ASP.NET Core 2.0

With ASP.NET Core 2.0 there have been some changes in how EF Core tools (dotnet ef migrations etc.) determine the DbContext and connection string at design time.

The below answer leads that the migrations and seeding are applied when calling any of the dotnet ef xxx commands.

The new pattern for getting a design time instance for the EF Core tools is by using an BuildHostWeb static method.

As per this announcement, EF Core will now use the static BuildWebHost method which configures the whole application, but doesn't run it.

  public class Program
  {
      public static void Main(string[] args)
      {
          var host = BuildWebHost(args);

          host.Run();
      }

      // Tools will use this to get application services
      public static IWebHost BuildWebHost(string[] args) =>
          new WebHostBuilder()
              .UseKestrel()
              .UseContentRoot(Directory.GetCurrentDirectory())
              .UseIISIntegration()
              .UseStartup<Startup>()
              .Build();
  }

Replace this in your old Main method

public static void Main(string[] args)
{
    var host = BuildWebHost(args)
        .Seed();

    host.Run();
}

Where Seed is an extension method:

public static IWebHost Seed(this IWebHost webhost)
{
    using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
    {
        // alternatively resolve UserManager instead and pass that if only think you want to seed are the users     
        using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>()) 
        {
            SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
        }
    }
}

public static class SeedData
{
    public static async Task SeedAsync(ApplicationDbContext dbContext)
    {
        dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
    }
}

Old Answer, still applies to ASP.NET Core 1.x

There is a semi-official pattern on how to seed Entity Framework Core in ASP.NET Core application you should apply, because during application startup there is no Request and hence no RequestServices (which resolves scoped services).

In essence it boils down to creating a new scope, resolve the types you need and dispose the scope again once you're finished.

// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
    var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

    if (await db.Database.EnsureCreatedAsync())
    {
        await SeedDatabase(db);
    }
}

One of the reasons directly resolving a service via app.ApplicationServices.GetService<MyService>() is that ApplicationServices is the application (or lifetime) scope provider and the services resolved here stay alive until the application is shut down.

Usually the scoped container will resolve from it's parent container, if the object already exists there. So if you instantiate the DbContext this way in the application, it will be available in ApplicationServices container and when a request happens, a child container will be created.

Now when resolving the DbContext it won't be resolved as scoped, because it already exists in the parent container, so the instance of the parent container will be returned instead. But since it has been disposed during the seeding, it won't be accessible.

A scope container is nothing else then a singleton container with limited lifetime.

So never resolve scoped services in Application startup w/o using the pattern above of first creating a scope and resolving from it.

Tseng
  • 61,549
  • 15
  • 193
  • 205
10

If you are using any async void please replace it with async Task

Abdus Salam Azad
  • 5,087
  • 46
  • 35
4

Had the same issue. Hope this helps someone. In addition to making the method async and return a Task, you need to make sure that the method will also be awaited wherever you are calling it.

dave_077
  • 83
  • 8
  • This was the problem for me! I forgot to add await all the way back in my Controller (which first went through mulitple layers before throwing the exception in the datalayer, so it was very hidden). Thanks! – Jon Koeter Jul 26 '21 at 14:16
3

Similar to Yang Zhang, I had to change my controller function From:

 public IActionResult MyFunc([FromBody]string apiKey)

To:

 public async Task<IActionResult> MyFunc([FromBody]string apiKey)
Marcus Talcott
  • 321
  • 2
  • 4
2

the problem is that DBContext is scoped per request by default, but you have things that depend on it scoped as transient, so they do not have the same scope and DBContext may be disposed before you are done using it

Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • 1
    I changed AddTransient to AddScoped and I got the same error ... Any idea why? – Miguel Moura Aug 01 '16 at 16:50
  • in this case what is the solution? – Haithem KAROUI Nov 20 '19 at 16:00
  • 2
    setting DbContext to Transient is not the correct solution in this case. However, after examining the source code, I noticed that there was a discrepancy between async Task and non async Task. Furthermore, if you are using async strategy dbcontext class, make sure that you maintain async Task on your controller methods and any class that interacts with the dbcontext. Rule of thumb is, if you use async Task anywhere in dbcontext, you have to bubble up the async Task to controller and service layer. I hope this helps anyone facing this issue. – CesarB Jun 21 '20 at 19:25
1

I'd like to share my solution for those who are trying to start a background task in their controllers. That means you want to start a task and don't want to wait for the result like audit logging to database. If you are creating a task and try to do database operations in that task you will receive this error;

Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.

Already explained in details. Find it here

Sabri Meviş
  • 2,231
  • 1
  • 32
  • 38
1

In my case, it wasn't an Async problem, but the code had a
using (DataContext dc=dataContext) {}
block, and of course, the context was disposed after that.

TheTall
  • 298
  • 3
  • 7
0

In my case the controller method was async and it was returning a task but inside that I had 2 await calls. First await calls gets some data from a service and second await call writes to the DB using EF. I had to remove the await from this second call and only then it worked. I didn't remove async/await from method signatures. I just called the second method without await.

Varun Sharma
  • 2,591
  • 8
  • 45
  • 63
-1

I was facing a similar error and later was able to resolve it.

I was calling the async method without using await.

old code

var newUser = _repo.Register(newUserToCreate);

with the fix made

var newUser = await _repo.Register(newUserToCreate);
Andrew
  • 18,680
  • 13
  • 103
  • 118
user3651810
  • 109
  • 11