[Hangfire] Hangfire OWIN-Host 搭配 SQLite Storage

前面幾篇都是用 SQL Server,這篇就試試 SQLite 吧

開發環境

  • VS 2019
  • .NET Framework 4.8
  • Console App Template Project

 

管理工具

DB Browser for SQLite

管理的工具只是用來確定 Code First 產生的資料結構如預期,我採用 DB Browser for SQLite,下載位置如下

https://sqlitebrowser.org/

 

開始實作

安裝套件

Install-Package System.Data.SQLite
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package Microsoft.Owin.Diagnostics
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Hangfire
Install-Package Hangfire.MemoryStorage
Install-Package Hangfire.Sqlite
Install-Package Hangfire.Console
Install-Package Hangfire.Dashboard.Management

 

組態設定

<connectionStrings>
    <add name="Hangfire" connectionString="data source=hangfire.db" providerName="System.Data.SQLite" />
</connectionStrings>

 

HangfireConfig

  • 使用 SQLite 的連線字串名稱
  • 設定 Hangfore URL 
internal class HangfireConfig
{
    private static readonly string DbConnectionName = "Hangfire";
    private static readonly string Url              = "/hangfire";
 
    public static void Register(IAppBuilder app)
    {
        GlobalConfiguration.Configuration
                           .UseDashboardMetric(DashboardMetrics.ServerCount)
                           .UseDashboardMetric(DashboardMetrics.RecurringJobCount)
                           .UseDashboardMetric(DashboardMetrics.RetriesCount)
                           .UseDashboardMetric(DashboardMetrics.EnqueuedCountOrNull)
                           .UseDashboardMetric(DashboardMetrics.FailedCountOrNull)
                           .UseDashboardMetric(DashboardMetrics.EnqueuedAndQueueCount)
                           .UseDashboardMetric(DashboardMetrics.ScheduledCount)
                           .UseDashboardMetric(DashboardMetrics.ProcessingCount)
                           .UseDashboardMetric(DashboardMetrics.SucceededCount)
                           .UseDashboardMetric(DashboardMetrics.FailedCount)
                           .UseDashboardMetric(DashboardMetrics.DeletedCount)
                           .UseDashboardMetric(DashboardMetrics.AwaitingCount)
                           .UseManagementPages(cc => cc.AddJobs(() => { return GetModuleTypes(); }))
                           .UseSQLiteStorage(DbConnectionName)
                           .UseConsole()
            ;
        var options = new BackgroundJobServerOptions
        {
            ServerName  = $"{Environment.MachineName}.{Guid.NewGuid().ToString()}",
            WorkerCount = 20,
            Queues      = new[] {"critical", "normal", "low"}
        };
        var dashboardOptions = new DashboardOptions
        {
            Authorization = new IDashboardAuthorizationFilter[] {new DashboardAuthorizationFilter()}
        };
 
        app.UseHangfireDashboard(Url, dashboardOptions);
 
        app.UseHangfireServer(options);
    }
 
    private static Type[] GetModuleTypes()
    {
        var assemblies = new[] {Assembly.GetEntryAssembly()};
        var moduleTypes = assemblies.SelectMany(f =>
                                                {
                                                    try
                                                    {
                                                        return f.GetTypes();
                                                    }
                                                    catch (Exception)
                                                    {
                                                        return new Type[] { };
                                                    }
                                                }
                                               ) /*.Where(f => f.IsClass && typeof(IClientModule).IsAssignableFrom(f))*/
                                    .ToArray();
 
        return moduleTypes;
    }
}

 

DemoJob

[ManagementPage("演示", "low")]
public class DemoJob
{
	[Hangfire.Dashboard.Management.Support.Job]
	[DisplayName("呼叫內部方法")]
	[Description("呼叫內部方法")]
	[AutomaticRetry(Attempts = 3)]   //自動重試
	[DisableConcurrentExecution(90)] //禁止使用並行
	public void Action(PerformContext context = null, IJobCancellationToken cancellationToken = null)
	{
		if (cancellationToken.ShutdownToken.IsCancellationRequested)
		{
			return;
		}

		context.WriteLine($"測試用,Now:{DateTime.Now}");

		//Thread.Sleep(30000);
	}
}

 

Startup

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
        var config = new HttpConfiguration();
        config.Filters.Add(new ErrorHandlerAttribute());
        HangfireConfig.Register(app);
        config.Routes.MapHttpRoute("DefaultApi",
                                   "api/{controller}/{id}",
                                   new {id = RouteParameter.Optional}
                                  );
 
        app.UseWelcomePage("/");
        app.UseWebApi(config);
        app.UseErrorPage();
    }
}

 

掛載服務

internal class Program
{
    private const  string      HOST_ADDRESS = "http://localhost:9527";
    private static IDisposable s_webApp;
 
    private static void Main(string[] args)
    {
        s_webApp = WebApp.Start<Startup>(HOST_ADDRESS);
        Console.WriteLine("Hangfire service start...");
        Console.ReadLine();
    }
}

 

調試服務

Ctrl + F5,用瀏覽器訪問 http://localhost:9527/hangfire

 

 

範例位置

https://github.com/yaochangyu/sample.dotblog/tree/master/Hangfire/Lab.HangfireSqliteStorage

 

延伸閱讀

[Hangfire] 使用 Hangfire OWIN 建立非同步任務

[Hangfire] ASP.NET Core Hangfire 排程管理 - Hangfire.Dashboard.Management

[EF6][SQLite] SQLite Code First 和 Migration

 

後記

發現 Hangfire.SQLite 有 Bug,當 WorkCount 有 20 個,工作就會執行 20 次,作者建議把 WorkCount 設為 1, https://github.com/wanlitao/HangfireExtension/issues/3,因為它已經很久沒有更新了,我已經改用 Hangfire.Storage.SQLite。使用方式跟 Hangfire.SQLite 差不多,差異在於連線字串的處理,它似乎沒有支援讀取 web.config/app.config 的功能,這可以自己處理不是甚麼大問題。

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo