Categories
asp.net asp.net-core asp.net-mvc c#

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");
}
}
}

I found the reason of an error. It was the CoordinatesHelper class, which is used in the the background task OnlineTaggerMS and is a Transient – so it resulted with an error. I have no idea why compiler kept throwing errors pointing at MyDbContext, keeping me off track for few hours.