diff --git a/Hangfire.drawio b/Hangfire.drawio
new file mode 100644
index 0000000..3beeab8
--- /dev/null
+++ b/Hangfire.drawio
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HangfireExample.WebService/Controllers/JobController.cs b/HangfireExample.WebService/Controllers/JobController.cs
new file mode 100644
index 0000000..af86580
--- /dev/null
+++ b/HangfireExample.WebService/Controllers/JobController.cs
@@ -0,0 +1,76 @@
+using Hangfire;
+using HangfireExample.WebService.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel;
+
+namespace HangfireExample.WebService.Controllers
+{
+ [Route("Job")]
+ [ApiController]
+ public class JobController : ControllerBase, IJobService
+ {
+ public JobController() { }
+
+ [HttpPost("Continuation", Name = "Continuation"), Produces("application/json")]
+ public string Continuation(string message, string jobId)
+ {
+ return BackgroundJob.ContinueJobWith(jobId, () => ContinuationJob(message, jobId));
+ }
+
+ [NonAction]
+ [DisplayName("Continuation, Job Id = {1}")]
+ public static void ContinuationJob(string message, string jobId)
+ {
+ Console.WriteLine("{0}: {1}", DateTimeOffset.Now, message);
+ }
+
+ [HttpPost("Delay", Name = "Delay"), Produces("application/json")]
+ public string Delay(string message, int seconds, int minutes = 0, int hours = 0, int days = 0)
+ {
+ TimeSpan delay =
+ seconds > 0 ? TimeSpan.FromSeconds(seconds) :
+ minutes > 0 ? TimeSpan.FromMinutes(minutes) :
+ hours > 0 ? TimeSpan.FromHours(hours) :
+ days > 0 ? TimeSpan.FromDays(days) : throw new Exception("未指定延遲時間");
+ return BackgroundJob.Schedule(() => DelayJob(message, seconds, minutes, hours, days), delay);
+ }
+
+ [NonAction]
+ [DisplayName("Delay, Day = {4}, Hour = {3}, Minute = {2}, Second = {1}")]
+ public static void DelayJob(string message, int seconds, int minutes = 0, int hours = 0, int days = 0)
+ {
+ Console.WriteLine("{0}: {1}", DateTimeOffset.Now, message);
+ }
+
+ [HttpPost("Fire", Name = "Fire"), Produces("application/json")]
+ public string Fire(string message)
+ {
+ return BackgroundJob.Enqueue(() => FireJob(message));
+ }
+
+ [NonAction]
+ [DisplayName("Fire")]
+ public static void FireJob(string message)
+ {
+ Console.WriteLine("{0}: {1}", DateTimeOffset.Now, message);
+ }
+
+ [HttpPost("Recurring", Name = "Recurring"), Produces("application/json")]
+ public string Recurring(string message, string jobId, string expression)
+ {
+ RecurringJob.RemoveIfExists(jobId);
+
+ RecurringJob.AddOrUpdate(jobId, () => _RecurringJob(message, jobId, expression), expression);
+
+ return jobId;
+ }
+
+ [NonAction]
+ [DisplayName("Recurring, JobId = {1}, Cron expression = {2}")]
+ public static void _RecurringJob(string message, string jobId, string expression)
+ {
+ Console.WriteLine("{0}: {1}", DateTimeOffset.Now, message);
+ }
+ }
+}
diff --git a/HangfireExample.WebService/Extensions/HangfireExtension.cs b/HangfireExample.WebService/Extensions/HangfireExtension.cs
index 013efbe..0f26f85 100644
--- a/HangfireExample.WebService/Extensions/HangfireExtension.cs
+++ b/HangfireExample.WebService/Extensions/HangfireExtension.cs
@@ -1,5 +1,7 @@
using Hangfire;
+using Hangfire.Dashboard;
using Hangfire.Storage.SQLite;
+using HangfireExample.WebService.Filters;
namespace HangfireExample.WebService.Extensions
{
@@ -18,13 +20,37 @@ namespace HangfireExample.WebService.Extensions
// 將 Hangfire 加入服務,使用 SQLite 作為儲存區
services.AddHangfire(configuration => configuration
.UseSQLiteStorage("HangfireExample.db"));
+ // 將 Hangfire Server 加入服務
+ services.AddHangfireServer(options =>
+ {
+ // 指定 Hangfire Server 的名稱
+ options.ServerName = "HangfireExample";
+ // 指定 Hangfire Server 執行佇列的 Tags
+ options.Queues = ["default", "wb",];
+ });
return services;
}
public static WebApplication UseHangfireDashboard(this WebApplication app, IConfiguration configuration)
{
- // HangfireDashboard 預設路由為 /Hangfire
- app.UseHangfireDashboard(configuration.GetValue("DashboardRoot"));
+ // Hangfire Dashboard 預設路由為 /Hangfire
+ app.UseHangfireDashboard(configuration.GetValue("DashboardRoot"), new DashboardOptions
+ {
+ Authorization = [new DashboardAuthorizationFilter()],
+ // 指定 Dashboard 指定讀取,不能操作,如重新執行、再次加入排程與刪除任務等
+ IsReadOnlyFunc = (DashboardContext context) => true,
+ // 指定 Back to site 的連結
+ AppPath = "/swagger",
+ });
+ // 設定第二資料來源的 Dashboard
+ //app.UseHangfireDashboard(configuration.GetValue("DashboardRoot"), new DashboardOptions
+ //{
+ // Authorization = [new DashboardAuthorizationFilter()],
+ // // 指定 Dashboard 指定讀取,不能操作,如重新執行、再次加入排程與刪除任務等
+ // IsReadOnlyFunc = (DashboardContext context) => true,
+ // // 指定 Back to site 的連結
+ // AppPath = "/swagger",
+ //}, new SQLiteStorage("HangfireExample2.db"));
return app;
}
}
diff --git a/HangfireExample.WebService/Filters/DashboardAuthorizationFilter.cs b/HangfireExample.WebService/Filters/DashboardAuthorizationFilter.cs
new file mode 100644
index 0000000..38c7e10
--- /dev/null
+++ b/HangfireExample.WebService/Filters/DashboardAuthorizationFilter.cs
@@ -0,0 +1,17 @@
+using Hangfire.Annotations;
+using Hangfire.Dashboard;
+
+namespace HangfireExample.WebService.Filters
+{
+ ///
+ /// Hangfire Dashboard 的認證過濾中介。
+ ///
+ public class DashboardAuthorizationFilter : IDashboardAuthorizationFilter
+ {
+ public bool Authorize([NotNull] DashboardContext context)
+ {
+ // 認證時,直接回傳成功(無驗證)
+ return true;
+ }
+ }
+}
diff --git a/HangfireExample.WebService/HangfireExample.WebService.csproj b/HangfireExample.WebService/HangfireExample.WebService.csproj
index e5635c9..801b9ed 100644
--- a/HangfireExample.WebService/HangfireExample.WebService.csproj
+++ b/HangfireExample.WebService/HangfireExample.WebService.csproj
@@ -12,8 +12,4 @@
-
-
-
-
diff --git a/HangfireExample.WebService/HangfireExample.db b/HangfireExample.WebService/HangfireExample.db
index 237a782..957072f 100644
Binary files a/HangfireExample.WebService/HangfireExample.db and b/HangfireExample.WebService/HangfireExample.db differ
diff --git a/HangfireExample.WebService/Properties/launchSettings.json b/HangfireExample.WebService/Properties/launchSettings.json
index 5c3f504..eff80e2 100644
--- a/HangfireExample.WebService/Properties/launchSettings.json
+++ b/HangfireExample.WebService/Properties/launchSettings.json
@@ -1,4 +1,24 @@
-{
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5185"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "hangfire",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ },
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
@@ -7,25 +27,5 @@
"applicationUrl": "http://localhost:1922",
"sslPort": 0
}
- },
- "profiles": {
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "http://localhost:5185",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "swagger",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/HangfireExample.WebService/Services/IJobService.cs b/HangfireExample.WebService/Services/IJobService.cs
new file mode 100644
index 0000000..150fdc8
--- /dev/null
+++ b/HangfireExample.WebService/Services/IJobService.cs
@@ -0,0 +1,13 @@
+namespace HangfireExample.WebService.Services
+{
+ ///
+ /// Job 服務。
+ ///
+ public interface IJobService
+ {
+ public string Fire(string message);
+ public string Delay(string message, int seconds, int minutes = 0, int hours = 0, int days = 0);
+ public string Recurring(string message, string jobId, string expression);
+ public string Continuation(string message, string jobId);
+ }
+}
diff --git a/HangfireExample.WebService/appsettings.json b/HangfireExample.WebService/appsettings.json
index e1258a6..6203d99 100644
--- a/HangfireExample.WebService/appsettings.json
+++ b/HangfireExample.WebService/appsettings.json
@@ -1,4 +1,7 @@
{
+ "ConnectionStrings": {
+ "Hangfire": "HangfireExample.db"
+ },
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/HangfireExample/Program.cs b/HangfireExample/Program.cs
index 05417a7..8593808 100644
--- a/HangfireExample/Program.cs
+++ b/HangfireExample/Program.cs
@@ -1,4 +1,5 @@
using Hangfire;
+using Hangfire.SqlServer;
using Hangfire.Storage.SQLite;
// 第三方擴充: Hangfire.Storage.SQLite 0.4.1
diff --git a/HangfireResources/BackToSite.jpg b/HangfireResources/BackToSite.jpg
new file mode 100644
index 0000000..0de8efa
Binary files /dev/null and b/HangfireResources/BackToSite.jpg differ
diff --git a/README.md b/README.md
index 3f891f1..3f8c98c 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,131 @@ GlobalConfiguration
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
- UseRecommendedIsolationLevel = true
+ UseRecommendedIsolationLevel = true,
+ DisableGlobalLocks = true, // Migration to Schema 7 is required
});
+
+// Hangfire SQLite
+GlobalConfiguration
+ .Configuration
+ .UseSQLiteStorage("HangfireExample.db"));
+```
+
+## Server
+
+啟動 Hangfire 伺服器
+
+```
+services.AddHangfireServer();
+```
+
+## Using Dashboard UI
+
+開啟 Dashboard 功能
+
+```
+app.UseHangfireDashboard();
+```
+
+* Configuring Authorization
+
+設定自定義驗證
+
+```
+app.UseHangfireDashboard("hangfire", new DashboardOptions
+{
+ Authorization = [new DashboardAuthorizationFilter()],
+});
+```
+
+* Read-only view
+
+設定 UI 為 ReadOnly
+
+```
+app.UseHangfireDashboard("hangfire", new DashboardOptions
+{
+ IsReadOnlyFunc = (DashboardContext context) => true,
+});
+```
+
+* Change URL Mapping
+
+修改 Dashboard UI 的預設根路由
+
+```
+app.UseHangfireDashboard("hangfire");
+```
+
+* Change Back to site Link
+
+修改返回按鈕的路由
+
+```
+app.UseHangfireDashboard("hangfire", new DashboardOptions
+{
+ AppPath = "/swagger", // 導向到 Swagger UI
+});
+```
+
+
+
+* Multiple Dashboards
+
+設定不同資料來源的 Dashboard
+
+```
+app.UseHangfireDashboard(configuration.GetValue("DashboardRoot"), new DashboardOptions
+{
+ Authorization = [new DashboardAuthorizationFilter()],
+ // 指定 Dashboard 指定讀取,不能操作,如重新執行、再次加入排程與刪除任務等
+ IsReadOnlyFunc = (DashboardContext context) => true,
+ // 指定 Back to site 的連結
+ AppPath = "/swagger",
+}, new SQLiteStorage("HangfireExample2.db"));
+```
+
+## Background Methods
+
+排程可以直接執行特定方法,Hangfire 會將方法(可使用非同步)、類型與參數序列化後保存到資料庫,Hangfire 依照排程類型,分為以下四種
+
+* Fire-and-Forget jobs: 將任務丟入執行佇列,直接執行
+
+```
+BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-Forget"));
+```
+
+* Delayed jobs: 延遲指定時間後,將任務丟入執行佇列
+
+```
+BackgroundJob.Schedule(() => Console.WriteLine("Delayed"), TimeSpan.FromDays(1));
+```
+
+* Recurring jobs: 依照指定間隔,將任務丟入執行佇列
+
+```
+RecurringJob.AddOrUpdate("job_id", () => Console.Write("Recurring By CRON"), Cron.Daily);
+
+RecurringJob.AddOrUpdate("job_id", () => Console.Write("Recurring By CRON expressions"), "0 12 * */2");
+```
+
+移除已存在的排程任務
+
+```
+RecurringJob.RemoveIfExists("job_id");
+```
+
+手動觸發已存在的排程任務
+
+```
+RecurringJob.Trigger("job_id");
+```
+
+
+* Continuations: 指定的任務完成後,才丟入執行佇列
+
+```
+var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-Forget"));
+
+BackgroundJob.ContinueJobWith(jobid, () => Console.WriteLine("Continuations"));
```
\ No newline at end of file