I am having trouble seeding data into the identity role table. I always get the error
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
<>4__this._roleManager was null
I am not sure why this is happening and why it's not seeding data into the table.How do I fix this?
Below is my code
public class UserRoleSeed
{
private readonly RoleManager<IdentityRole> _roleManager;
private string[] _roleArray = { "Admin, TerminalManager, Dispatcher, Driver, Mechanic, Recruiter, MechanicManger" };
public UserRoleSeed(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}
public async void Seed()
{
foreach (string index in _roleArray)
{
if ((await _roleManager.FindByNameAsync(index)) == null)
{
await _roleManager.CreateAsync(new IdentityRole { Name = index });
}
}
}
}
for my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TransportDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddEntityFrameworkStores<TransportDbContext>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(
routes =>
{
routes.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
});
}
// seeds data into the identity role table
new UserRoleSeed(app.ApplicationServices.GetService<RoleManager<IdentityRole>>()).Seed();
}
}
}
You're using an async method to seed your roles, but you're not awaiting it. That means that your code keeps moving on, eventually taking variables you're depending on in your async method along with it when branches go out of scope. Hence, NullReferenceExceptions.
Additionally, services like RoleManager<TRole> are "scoped" services, meaning they can only be retrieved from a particular active scope. In an actual request, a scope would be created for the request, allowing these services to be injected into anything within the request pipeline. However, here, you have no active scope, and therefore must create one.
Instead of attempting to seed as part of your Configure method, you should move this code out into your Program class. The code below addresses both of the above concerns:
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
await new UserRoleSeed(scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>()).Seed();
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Essentially you'll use an async Main to run your app, which then gives you the ability to await additional things like your seed. For what it's worth, this can be shortened somewhat in C# 7.2 with an actual async Main, i.e.:
public static async Task Main(string[] args)
Without having to proxy from Main to a MainAsync, but under the hood the compiler just sets up this same construction for you.
That's the shortest path to get this code working, but you still have a couple of minor issues. First, you should avoid using async void, which is an antipattern. You're essentially swallowing the async output with that, including any exceptions that may be thrown. You should virtually always use async Task as the return when you don't care about the actual return. The few situations where async void is appropriate are known to individuals who need to use it. In other words, if you don't know when you should use async void, then you shouldn't be using async void.
Also, while there's nothing technically wrong with newing up a class and passing the dependency into the constructor, it's more appropriate in this case to make the class static and pass the required dependencies into the seed method:
await UserRoleSeed.Seed(roleManager);
Finally, again, while not critical, it's convention to name async methods with an Async suffix. This makes it clear that the method is async and prevents accidentally not awaiting the method simply because it's not obvious that it needs to be awaited (which may have been the case here). In short, change the name from Seed to SeedAsync, since it does async work.
Ok guys I figured it out, Below is my solution.
I basically modified the class for seeding the data and renamed it DbInitializer.cs
public class DbInitializer
{
private static readonly string[] _roleArray = { "Admin", "Terminal Manager", "Dispatcher", "Driver", "Mechanic", "Recruiter", "Mechanic Manger" };
public static async Task InitializeAync (TransportDbContext context, IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<Role>>();
foreach (string index in _roleArray)
{
if ((await roleManager.FindByNameAsync(index)) == null)
{
await roleManager.CreateAsync(new Role { Name = index });
}
}
}
}}
then I called the function in my Program.cs file as suggested by #Chris Pratt.
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TransportDbContext>();
await DbInitializer.InitializeAync(context, services);
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
} }
thanks to everyone who tried to help me
Related
I use Serilog in my Blazor Server Side Application, which deploy on IIS using site bindings
And I want to ensure that the logs(unhandled exceptions and my custom log info) on these sites are written to different folders by hostname.
My Serilog Configuration:
public static class HostBuilderExtension
{
public static IHostBuilder AddSerilog(this IHostBuilder hostBuilder)
{
return hostBuilder.UseSerilog((hostingContext, loggerConfiguration) =>
{
var appSettings = hostingContext.Configuration.Get<AppSettings>();
loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.Map("Hostname", "ms-hosting", (hostname, wr) =>
wr.Async(to =>
to.File(appSettings.GeneralLogsPath(hostname), rollingInterval: RollingInterval.Day, shared: true)));
});
}
}
GeneralLogsPath
public string GeneralLogsPath(string hostname) => Path.Combine(AppLogsRoot, hostname, "General", "log.log");
Registration in Program.cs:
builder.Host.AddSerilog();
And my custom Middleware to push current hostname to LogContext:
using Serilog.Context;
using System.Collections.Generic;
namespace Herbst.Acc2.CustomerWebUI.Classes;
public class ScopedLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ScopedLoggingMiddleware> _logger;
public ScopedLoggingMiddleware(RequestDelegate next, ILogger<ScopedLoggingMiddleware> logger)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Invoke(HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var hostname = context.Request.Host.Host;
try
{
using (LogContext.PushProperty("Hostname", hostname))
{
await _next(context);
}
}
//To make sure that we don't loose the scope in case of an unexpected error
catch (Exception ex) when (LogOnUnexpectedError(ex))
{
return;
}
}
private bool LogOnUnexpectedError(Exception ex)
{
_logger.LogError(ex, "An unexpected exception occured!");
return true;
}
}
public static class ScopedLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseScopedLogging(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ScopedLoggingMiddleware>();
}
}
In Program.cs
app.UseScopedLogging();
Can I be sure that the message from test-t1.com will never written to \logs\test-t2.com?
Here’s how logical call context works with asynchronous code.
Logical call context data flows with ExecutionContext. This means that it’s not affected by ConfigureAwait(continueOnCapturedContext: false); you can’t “opt-out” of the logical call context. So the logical call context at the beginning of an async method will always flow through to its continuations.
When an async method starts, it notifies its logical call context to activate copy-on-write behavior. This means the current logical call context is not actually changed, but it is marked so that if your code does call CallContext.LogicalSetData, the logical call context data is copied into a new current logical call context before it is changed. Note: the copy-on-write behavior of logical call contexts is only available on .NET 4.5.
Read more about AsyncLocal here
Pay attention to these:
So the logical call context(AsyncLocal) at the beginning of an async method will always flow through to its continuations(In your example this is all the middleware following the current one).
Therefore, in your case, each request will have its own logical call context, so you can be sure that the message from test-t1.com will never written to \logs\test-t2.com?
In my Startup.cs I perform migrations like this. There's some weird issue in the private method I can't explain and it seems to be related to the asynchronous version of the invokation. The following works just as expected (although the asynchornicity in pointless.
public async void Configure(
IApplicationBuilder app, IWebHostEnvironment env)
{
...
await MigrateDb(app);
...
}
private static async Task MigrateDb(IApplicationBuilder app)
{
using IServiceScope scope = app.ApplicationServices
.GetService<IServiceScopeFactory>()?.CreateScope();
if (scope == null)
throw new OperationCanceledException();
Context context = scope.ServiceProvider.GetRequiredService<Context>();
context.Database.Migrate();
}
However, when I apply the asynchoronous migration, stuff stop to work. FOr some reason, the controllers are not reachable and I get 404's on every endpoint, including the ping that only returns a fixed string form the service without any contact with the DB.
private static async Task MigrateDb(IApplicationBuilder app)
{
using ...
Context context = ...
await context.Database.MigrateAsync();
}
Googling gave absolutely nothing related. I tried explicitly killing the scope by scope.Dispose();. At the moment I have no idea what to google for more or how to approach the trouble-shooting. It's not a critical production issue - I'm only doing a test (and this part isn't the aim of it). Still, it would be very interesting to understand why this happens.
I am somewhat skeptical about the Configure method. There is difference between async task and async void. There is one answer async/await - when to return a Task vs void? will help to understand that.
Overall async void not handling async of MigrateAsnyc properly and that may leads to problem.
If I have to try another solution then I will program.cs and you can try that. You have to use your own context.
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
//Migrate database
var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
await context.Database.MigrateAsync();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
When I try to run a background task, I always create a new scope inside that task. With the update to 3+, it seems that within the new create scope, there is a reference to the original request. The following code would break on the Debugger.Break() statement:
public class TestController : Controller
{
public readonly IServiceScopeFactory ServiceScopeFactory;
public TestController(
IServiceScopeFactory serviceScopeFactory)
{
this.ServiceScopeFactory = serviceScopeFactory;
}
// GET
public IActionResult Index()
{
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (actionContext.ActionDescriptor != null)
Debugger.Break();
}
});
return Content("Test");
}
}
The startup looks like this:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The problem is that httpContext is shared with the new create scope. When one of the scopes is being disposed of, it affects the other scope. For example with IUrlHelper, which results in an "IFeatureCollection has been disposed of".
For test sake, I added a test if the httpContext would be the same. And it seems it is!
public IActionResult Index()
{
// Just for testing
var originalContext = this.HttpContext;
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
// Make sure the original request was disposed
Thread.Sleep(1000);
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (originalContext == actionContext.HttpContext)
Debugger.Break();
}
});
return Content("Test");
}
For me, this seems like odd behaviour, cause I would except the new scope not to have the same httpContext. It should be a NEW scope. Should the scope be created in another way?
Found solution
In my production code I use a transient ActionContext scope, which attempt to detect if it's dealing with a request, or a background scope as followed:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
var actionContext = actionContextAccessor?.ActionContext;
// Create custom actioncontext
if (actionContext == null) {
// create a manual actionContext
}
return actionContext;
});
This doesn't seem to work anymore. The solution seems to be too validate if the httpContext exist through the IHttpContextAccessor:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>();
if (currentContextAccess.HttpContext == null) {
// create a manual actionContext
...
return actionContext;
}
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
return actionContextAccessor.ActionContext;
});
For me, this seems like odd behaviour, cause I would except the scope not to be. Should the scope be created in another way?
Why odd? IActionContextAccessor is a singleton (same as IHttpContextAccessor is), so its normal to return the same instance even inside a newly created scope.
Since you are not awaiting, the Task.Run, your request will finish before the task is finished. How do you want to access the HttpContext after the request is done? It's only valid during the request. You have to get all the required data prior to spinning up the new Task and pass the values you need to the background task.
HttpContext is only valid for the duration of the request and since you dont await it, request ends early.
And what your code does is undefined behavior, see David's Guidelines
Do not access the HttpContext from multiple threads in parallel. It is not thread safe.
Do not use the HttpContext after the request is complete
I have a simple service that contains a List<Foo>. In Startup.cs, I am using the services.addScoped<Foo, Foo>() method.
I am inject the service instance in two different places (controller and middleware), and for a single request, I would expect to get the same instance. However, this does not appear to be happening.
Even though I am adding a Foo to the List in the Controller Action, the Foo list in the Middleware is always empty. Why is this?
I have tried changing the service registration to a singleton, using AddSingleton() and it works as expected. However, this has to be scoped to the current request. Any help or ideas are greatly appreciated!
FooService.cs
public class FooService
{
public List<Foo> Foos = new List<Foo>();
}
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<FooService, FooService>();
}
[Below are the two places where I am injecting the service, resulting in two different instances]
MyController.cs
public class MyController : Controller
{
public MyController(FooService fooService)
{
this.fooService = fooService;
}
[HttpPost]
public void TestAddFoo()
{
//add foo to List
this.fooService.Foos.Add(new Foo());
}
}
FooMiddleware.cs
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
this.next = next;
this.serviceProvider = serviceProvider;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = this.serviceProvider.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
That's because when you inject IServiceProvider into your middleware - that's "global" provider, not request-scoped. There is no request when your middleware constructor is invoked (middleware is created once at startup), so it cannot be request-scoped container.
When request starts, new DI scope is created, and IServiceProvider related to this scope is used to resolve services, including injection of services into your controllers. So your controller resolves FooService from request scope (because injected to constructor), but your middleware resolves it from "parent" service provider (root scope), so it's different. One way to fix this is to use HttpContext.RequestServices:
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = context.RequestServices.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
But even better way is to inject it into Invoke method itself, then it will be request scoped:
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
First of all you shouldn't be using GetService, use the proper DI system that is in place by passing it into the Invoke method as a parameter.
Secondly, the reason you are getting a different object is because the constructor of the middleware is called outside of the scope of any request, during the app initialisation phase. So the container used there is the global provider. See here for a good discussion.
public class AppMessageMiddleware
{
private readonly RequestDelegate _next;
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
_next = next;
}
//Note the new parameter here: vvvvvvvvvvvvvvvvvvvvv
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count;
return Task.CompletedTask;
});
await _next(context);
}
}
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
}
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
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)
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.
If you are using any async void please replace it with async Task
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.
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
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)
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
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.
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.
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);