What is the correct place to add a database driven scheduler in ASP.NET Core?

核能气质少年 提交于 2019-12-01 21:52:47

When you are running code on a background thread, you should always begin a new 'scope' for your DI container on that background thread and resolve from that scope.

So what you should do is:

  • Create a new scope inside the event
  • Resolve OperationClass from that scope
  • Inside OperationClass only rely on constructor injection; not on Service Location.

Your code should look something like this:

public class Scheduler
{
    static Timer _timer;
    const int dueTimeMin = 1;
    const int periodMin = 1;

    public static void Start(IServiceScopeFactory scopeFactory)
    {
        if (scopeFactory == null) throw new ArgumentNullException("scopeFactory");
        _timer = new Timer(_ =>
        {
            using (var scope = new scopeFactory.CreateScope())
            {
                scope.GetRequiredService<OperationClass>().DoOperation();
            }
        }, new AutoResetEvent(false), dueTimeMin * 60 * 1000, periodMin * 60 * 1000);
    }
}

Here Start depends on IServiceScopeFactory. IServiceScopeFactory can be resolved from the IServiceProvider.

Your OperationClass will becomes something like the following:

public class OperationClass
{
    private readonly ILogger<OperationClass> _logger;
    private readonly AppDbContext _appDbContext;

    public OperationClass(ILogger<OperationClass> logger, AppDbContext appDbContext)
    {
        if (logger == null) throw new ArgumentNullException(nameof(logger));
        if (appDbContext == null) throw new ArgumentNullException(nameof(appDbContext));

        _logger = logger;
        _appDbContext = appDbContext;
    }

    public void DoOperation()
    {
        try     
        {
            _logger.LogInformation("DoOperation.");

            _appDbContext.PlayNows.Add(new PlayNow
            {
                DateTime = DateTime.Now
            });

            _appDbContext.SaveChanges();
        }
        catch (Exception exception)
        {
            _logger.LogError($"Error in DoOperation: {exception}");
        }
    }
}

Although not documentation particular to the .NET Core Container, this documentation provides a more detailed information about how to work with a DI Container in a multi-threaded application.

You should call it inside ConfigureServices after your AppDbContext has been resolved, in the code you are calling it before DI registrations. also you can use services.BuildServiceProvider() to create an IServiceProvider containing services from the provided DI:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    Configuration = builder.Build();

    MmHelper.InitializeMapper();
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>();

    services.AddIdentity<User, IdentityRole>()
        .AddEntityFrameworkStores<AppDbContext>()
        .AddDefaultTokenProviders();

    // ....

    Scheduler.Start(services.BuildServiceProvider());
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!