Core中使用Hangfire

 

  之前使用Quartz.Net,后来发现hangfire对Core的继承更加的好,而且自带管理后台,这就比前者好用太多了。

安装注册

安装

PM> Install-Package Hangfire

Startup.cs,在ConfigureServices方法中添加注册:

services.AddHangfire(x => x.UseSqlServerStorage("connection string"));

 

SqlServer是使用这种方式,其他方式见官方的文档及相应插件。

注册完成后,还需要在Configure方法中,添加如下高亮部分的代码:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            //添加Hangfire
            app.UseHangfireServer();
            app.UseHangfireDashboard();
配置完毕后运行我们的项目,这时Hangfire会自动在数据库中创建结构,数据库中会新建如下表:

现在在网站根目录+/Hangfire即可查看管理后台界面如下:

image

基本使用

Hangfire的使用非常简单,基本上使用以下几个静态方法:

//执行后台脚本,仅执行一次
BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget!")); 

//延迟执行后台脚本呢,仅执行一次
BackgroundJob.Schedule(
    () => Console.WriteLine("Delayed!"),
    TimeSpan.FromDays(7));
    
//周期性任务
RecurringJob.AddOrUpdate(
    () => Console.WriteLine("Recurring!"),
    Cron.Daily);
    
//等上一任务完成后执行
BackgroundJob.ContinueWith(
    jobId,  //上一个任务的jobid
    () => Console.WriteLine("Continuation!"));

 注意,周期性使用可以使用Cron表达式

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * command to execute

 

EntryDescriptionEquivalent to
@yearly (or @annually) 每年1月3号2:01分运行 1 2 3 1 *
@monthly 每月3号2:01分运行 1 2 3 * *
@weekly 每周日的2:01分运行 1 2 * * 0
@daily 每天的2:01分运行 1 2 * * *
@hourly 每小时的1分运行 1 * * * *
@reboot Run at startup N/A

依赖注入

在.Net Core中处处是DI,一不小心,你会发现你在使用Hangfire的时候会遇到各种问题,比如下列代码:

public class HomeController : Controller
{
    private ILogger<HomeController> _logger;
    public HomeController(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<HomeController>();
    }
    public IActionResult Index()
    {
        _logger.LogInformation("start index");
        BackgroundJob.Enqueue(() => _logger.LogInformation("this a job!"));
        return View();
    }

}

 

项目启动后,你能正常访问,但在Hangfire后台你会看到如下错误:

image
错误信息呢大概意思是不能使用接口或者抽象方法类,其实就是因为Hangfire没有找到实例,那如何让Hangfire支持DI呢?

我们先创建一个MyActivator类,使其继承Hangfire.JobActivator类,代码如下:

public class MyActivator : Hangfire.JobActivator
{
    private readonly IServiceProvider _serviceProvider;
    public MyActivator(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;

    public override object ActivateJob(Type jobType)
    {
        return _serviceProvider.GetService(jobType);
    }
}

 

重写了ActivateJob方法,使其返回的类型从我们的IServiceProvider中获取。

我们试着写两个后台脚本,CheckService和TimerService,CheckService的Check方法在执行计划时,会再次调用Hangfire来定时启动TimerService:

CheckService:

public interface ICheckService
{
    void Check();
}
public class CheckService : ICheckService
{
    private readonly ILogger<CheckService> _logger;
    private ITimerService _timeservice;
    public CheckService(ILoggerFactory loggerFactory,
        ITimerService timerService)
    {
        _logger = loggerFactory.CreateLogger<CheckService>();
        _timeservice = timerService;
    }

    public void Check()
    {
        _logger.LogInformation($"check service start checking, now is {DateTime.Now}");
        BackgroundJob.Schedule(() => _timeservice.Timer(), TimeSpan.FromMilliseconds(30));
        _logger.LogInformation($"check is end, now is {DateTime.Now}");
    }
}

 

TimerService:

public interface ITimerService
{
    void Timer();
}
public class TimerService : ITimerService
{
    private readonly ILogger<TimerService> _logger;
    public TimerService(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<TimerService>();
    }
    public void Timer()
    {
        _logger.LogInformation($"timer service is starting, now is {DateTime.Now}");
        _logger.LogWarning("timering");
        _logger.LogInformation($"timer is end, now is {DateTime.Now}");
    }
}

 

目前还无法使用,我们必须在Startup中注册这2个service:

services.AddScoped<ITimerService, TimerService>();
services.AddScoped<ICheckService, CheckService>();

 

我们在HomeController修改以下:

public IActionResult Index()
{
    _logger.LogInformation("start index");
    BackgroundJob.Enqueue<ICheckService>(c => c.Check());
    return View();
}

 

好,一切就绪,只差覆盖原始的Activator了,我们可以在Startup.cs中的Configure方法中使用如下代码:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
    GlobalConfiguration.Configuration.UseActivator<MyActivator>(new MyActivator(serviceProvider));
    ……
    ……
}

默认情况下Configure方法时没有IServiceProvider参数的,请手动添加

再次启动,我们的Job就会成功执行,截图如下:
image

补充:以上在开发环境可以正常使用,一旦发布到正式环境会报401 Unauthorized未授权错误,原因是 Hangfire 默认增加了授权配置。

解决方式:

增加CustomAuthorizeFilter

public class CustomAuthorizeFilter : IDashboardAuthorizationFilter
{
    public bool Authorize([NotNull] DashboardContext context)
    {
        //var httpcontext = context.GetHttpContext();
        //return httpcontext.User.Identity.IsAuthenticated;
        return true;
    }
}

 

Configure增加配置:

app.UseHangfireDashboard("/hangfire", new DashboardOptions() { 
    Authorization = new[] { new CustomAuthorizeFilter() }
});

 

参考资料

    

posted @ 2018-10-19 13:51  邑尘  阅读(2730)  评论(0编辑  收藏  举报