按照该教程,我已经在ASP.NET Core 2.1中构建了一个后台任务:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#consuming-a-scoped-service-in-a-background-task
编译给我一个错误:
System.InvalidOperationException:'无法使用单例'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'中的作用域服务'MyDbContext'。'
什么原因导致该错误以及如何解决?
背景任务:
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();
}
完全启动。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");
}
}
}
答案 0 :(得分:3)
对于实体框架 DbContext 类,现在有一个更好的方法:
services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer(...))
然后你可以像这样在你的单例类中注入工厂:
IDbContextFactory<MyDbContext> myDbContextFactory
最后像这样使用它:
using var myDbContex = _myDbContextFactory.CreateDbContext();
答案 1 :(得分:2)
您需要注入IServiceScopeFactory才能生成作用域。否则,您将无法单单解析作用域服务。
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
答案 2 :(得分:0)
您仍然需要向服务提供商注册MyDbContext
。通常这样做是这样的:
services.AddDbContext<MyDbContext>(options => {
// Your options here, usually:
options.UseSqlServer("YourConnectionStringHere");
});
如果您还发布了Program.cs和Startup.cs文件,那么它可能会使您有所了解,因为我能够快速设置实施该代码的测试项目,并且无法重现您的问题。
答案 3 :(得分:0)
我发现了错误的原因。它是CoordinatesHelper
类,在后台任务OnlineTaggerMS
中使用,并且是Transient
-因此导致错误。我不知道为什么编译器会不断抛出指向MyDbContext
的错误,而使我偏离了几个小时。
答案 4 :(得分:-1)
尽管@peace答案对他有用,但是如果您的IHostedService
中确实有DBContext,则需要使用IServiceScopeFactory
。
要查看如何执行此操作的惊人示例,请查看答案How should I inject a DbContext instance into an IHostedService?。
如果您想从博客中了解更多信息,请check this out。