Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot consume scoped service 'MyDbContext' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'

I've build a background task in my ASP.NET Core 2.1 following this tutorial: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#consuming-a-scoped-service-in-a-background-task

Compiling gives me an error:

System.InvalidOperationException: 'Cannot consume scoped service 'MyDbContext' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'

What causes that error and how to fix it?

Background task:

internal class OnlineTaggerMS : IHostedService, IDisposable {     private readonly CoordinatesHelper _coordinatesHelper;     private Timer _timer;     public IServiceProvider Services { get; }      public OnlineTaggerMS(IServiceProvider services, CoordinatesHelper coordinatesHelper)     {         Services = services;         _coordinatesHelper = coordinatesHelper;     }      public Task StartAsync(CancellationToken cancellationToken)     {         // Run every 30 sec         _timer = new Timer(DoWork, null, TimeSpan.Zero,             TimeSpan.FromSeconds(30));          return Task.CompletedTask;     }      private async void DoWork(object state)     {         using (var scope = Services.CreateScope())         {             var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();              Console.WriteLine("Online tagger Service is Running");              // Run something             await ProcessCoords(dbContext);         }               }      public Task StopAsync(CancellationToken cancellationToken)     {         _timer?.Change(Timeout.Infinite, 0);         return Task.CompletedTask;     }      private async Task ProcessCoords(MyDbContext dbContext)     {         var topCoords = await _coordinatesHelper.GetTopCoordinates();          foreach (var coord in topCoords)         {             var user = await dbContext.Users.SingleOrDefaultAsync(c => c.Id == coord.UserId);              if (user != null)             {                 var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();                 //expire time = 120 sec                 var coordTimeStamp = DateTimeOffset.FromUnixTimeMilliseconds(coord.TimeStamp).AddSeconds(120).ToUnixTimeMilliseconds();                  if (coordTimeStamp < now && user.IsOnline == true)                 {                     user.IsOnline = false;                     await dbContext.SaveChangesAsync();                 }                 else if (coordTimeStamp > now && user.IsOnline == false)                 {                     user.IsOnline = true;                     await dbContext.SaveChangesAsync();                 }             }         }     }      public void Dispose()     {         _timer?.Dispose();     } } 

Startup.cs:

services.AddHostedService<OnlineTaggerMS>(); 

Program.cs:

 public class Program {     public static void Main(string[] args)     {         var host = BuildWebHost(args);          using (var scope = host.Services.CreateScope())         {             var services = scope.ServiceProvider;             try             {                 var context = services.GetRequiredService<TutorDbContext>();                 DbInitializer.Initialize(context);             }             catch(Exception ex)             {                 var logger = services.GetRequiredService<ILogger<Program>>();                 logger.LogError(ex, "An error occurred while seeding the database.");             }         }          host.Run();     }      public static IWebHost BuildWebHost(string[] args) =>         WebHost.CreateDefaultBuilder(args)             .UseStartup<Startup>()             .Build(); } 

Full startup.cs:

public class Startup {     public Startup(IConfiguration configuration)     {         Configuration = configuration;     }      public IConfiguration Configuration { get; }      // This method gets called by the runtime. Use this method to add services to the container.     public void ConfigureServices(IServiceCollection services)     {         services.AddCors(options =>         {             options.AddPolicy("CorsPolicy",                 builder => builder.AllowAnyOrigin()                 .AllowAnyMethod()                 .AllowAnyHeader()                 .AllowCredentials());         });          services.AddDbContext<MyDbContext>(options =>             options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));          // ===== Add Identity ========         services.AddIdentity<User, IdentityRole>()             .AddEntityFrameworkStores<TutorDbContext>()             .AddDefaultTokenProviders();          JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims         services             .AddAuthentication(options =>             {                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;                 options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;             })             .AddJwtBearer(cfg =>             {                 cfg.RequireHttpsMetadata = false;                 cfg.SaveToken = true;                 cfg.TokenValidationParameters = new TokenValidationParameters                 {                     ValidIssuer = Configuration["JwtIssuer"],                     ValidAudience = Configuration["JwtIssuer"],                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),                     ClockSkew = TimeSpan.Zero // remove delay of token when expire                 };             });          //return 401 instead of redirect         services.ConfigureApplicationCookie(options =>         {             options.Events.OnRedirectToLogin = context =>             {                 context.Response.StatusCode = 401;                 return Task.CompletedTask;             };              options.Events.OnRedirectToAccessDenied = context =>             {                 context.Response.StatusCode = 401;                 return Task.CompletedTask;             };         });          services.AddMvc();          services.AddSwaggerGen(c =>         {             c.SwaggerDoc("v1", new Info { Version = "v1", Title = "xyz", });              // Swagger 2.+ support             var security = new Dictionary<string, IEnumerable<string>>             {                 {"Bearer", new string[] { }},             };              c.AddSecurityDefinition("Bearer", new ApiKeyScheme             {                 Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",                 Name = "Authorization",                 In = "header",                 Type = "apiKey"             });             c.AddSecurityRequirement(security);         });          services.AddHostedService<OnlineTaggerMS>();         services.AddTransient<UsersHelper, UsersHelper>();         services.AddTransient<CoordinatesHelper, CoordinatesHelper>();     }      // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.     public void Configure(IServiceProvider serviceProvider, IApplicationBuilder app, IHostingEnvironment env, TutorDbContext dbContext)     {         dbContext.Database.Migrate();          if (env.IsDevelopment())         {             app.UseDeveloperExceptionPage();         }          app.UseCors("CorsPolicy");         app.UseAuthentication();         app.UseMvc();          app.UseSwagger();         app.UseSwaggerUI(c =>         {             c.SwaggerEndpoint("v1/swagger.json", "xyz V1");         });          CreateRoles(serviceProvider).GetAwaiter().GetResult();     }      private async Task CreateRoles(IServiceProvider serviceProvider)     {         var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();         var UserManager = serviceProvider.GetRequiredService<UserManager<User>>();         string[] roleNames = { "x", "y", "z", "a" };         IdentityResult roleResult;          foreach (var roleName in roleNames)         {             var roleExist = await RoleManager.RoleExistsAsync(roleName);             if (!roleExist)             {                 roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));             }         }         var _user = await UserManager.FindByEmailAsync("xxx");          if (_user == null)         {             var poweruser = new User             {                 UserName = "xxx",                 Email = "xxx",                 FirstName = "xxx",                 LastName = "xxx"             };             string adminPassword = "xxx";              var createPowerUser = await UserManager.CreateAsync(poweruser, adminPassword);             if (createPowerUser.Succeeded)             {                 await UserManager.AddToRoleAsync(poweruser, "xxx");             }         }     } 
like image 291
Peace Avatar asked Jul 31 '18 16:07

Peace


People also ask

How do I get scoped service in singleton?

The Solution. To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory into your singleton service (the IServiceScopeFactory is itself a singleton, which is why this works).

Can singleton consume transient?

You should almost never consume scoped service or transient service from a singleton. You should also avoid consuming transient service from a scoped service.

Is IHostedService a singleton?

When you register implementations of IHostedService using any of the AddHostedService extension methods - the service is registered as a singleton. There may be scenarios where you'd like to rely on a scoped service. For more information, see Dependency injection in .

Is DbContext a singleton?

First, DbContext is a lightweight object; it is designed to be used once per business transaction. Making your DbContext a Singleton and reusing it throughout the application can cause other problems, like concurrency and memory leak issues. And the DbContext class is not thread safe.


1 Answers

You need to inject IServiceScopeFactory to generate a scope. Otherwise you are not able to resolve scoped services in a singleton.

using (var scope = serviceScopeFactory.CreateScope()) {   var context = scope.ServiceProvider.GetService<MyDbContext>(); } 

Edit: It's perfectly fine to just inject IServiceProvider and do the following:

using (var scope = serviceProvider.CreateScope()) // this will use `IServiceScopeFactory` internally {   var context = scope.ServiceProvider.GetService<MyDbContext>(); } 

The second way internally just resolves IServiceProviderScopeFactory and basically does the very same thing.

like image 147
alsami Avatar answered Sep 28 '22 09:09

alsami