前情提要
上一篇講完Hangfire的簡單應用,這篇要來講解改造Dashboard
的經驗,筆者公司專案,因為大系統,筆者把各個功能區塊分隔為各自專案,API站台
及Scheduler
等專案,對於Hangfire
所屬的Scheduler
站台的Dashboard
的Authentication
會變得困難,筆者的想法是乾脆就不要打開Dashboard
或者是Dashboard
本機瀏覽即可,然後於API
站台對應的前台(Angular站台)
中建立一個客製化的Hangfire Dashboard
。API站台
收到需求後轉發Request
至Scheduler站台並實作Hangfire Dashboard
相關API。
設計在前台有幾個好處:
- 權限由原有權限去控
- 開放手動觸發,可以記錄是哪位使用者執行
- 更直觀的介面
- CronExpression使用第三方套件轉成一般文字化的方式呈現
內容
先來實作Hangfire Dashboard
的Scheduler API
吧
Scheduler專案改造
安裝CronExpression翻譯套件
1
| dotnet add package CronExpressionDescriptor
|
定義RecurringJob的ResponseModel
基本上RecurringJob
對應的Model滿複雜的,筆者這邊另外訂了一個,只宣告前端頁面需顯示的屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class JobInfoResponseModel { public string Id { get; set; } public string LastJobState { get; set; } public DateTime? LastExecution { get; set; } public DateTime? LastExecutionLocalTime { get { return LastExecution?.ToLocalTime(); } } public DateTime? NextExecution { get; set; } public DateTime? NextExecutionLocalTime { get { return NextExecution?.ToLocalTime(); } } public string Cron { get; set; } public string CronDescription { get { var cronParser = new CronExpressionDescriptor.ExpressionParser(Cron, new Options() { Locale = " zh-Hant" }); try { cronParser.Parse(); return CronExpressionDescriptor.ExpressionDescriptor.GetDescription(Cron); } catch (FormatException ex) {
return Cron; } } } }
|
撰寫取得Hangfire的RecurringJobsAction
接下來於Scheduler
站台中寫一個Action
,讓API
站台呼叫,取得Hangfire Recurring Jobs
資訊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public class SchedulerInfoController : SchedulerBaseController { private readonly IConfiguration _config; private string _connStr; private readonly SqlConnectionFactory _factory; public SchedulerInfoController(IConfiguration config, SqlConnectionFactory factory) { _config = config; _connStr = Encoding.UTF8.GetString(Convert.FromBase64String(config.GetConnectionString("Hangfire"))); _factory = factory; }
[HttpPost("jobs")] public IActionResult GetSchedulerJobs() { var result = new List<JobInfoResponseModel>(); var jobStorage = new SqlServerStorage(_connStr); foreach (var item in StorageConnectionExtensions.GetRecurringJobs(jobStorage.GetConnection())) { result.Add(new JobInfoResponseModel() { Id = item.Id, LastJobState = item.LastJobState, LastExecution = item.LastExecution, NextExecution = item.NextExecution, Cron = item.Cron }); } return Ok(result); }
[HttpPost("execute")] public IActionResult ExecuteJob(JobInfoRequestModel jobInfo) { var jobStorage = new SqlServerStorage(_connStr); foreach (var item in StorageConnectionExtensions.GetRecurringJobs(jobStorage.GetConnection())) { if(item.Id == jobInfo.Id) { RecurringJob.Trigger(item.Id); } } return Ok(""); } }
|
API站台專案
筆者因主要排程使用Hangfire完成,且獨立為一個專案,API站台以這個需求,筆者需要一個定時Job去取得Hangfire的RecurringJobs資訊,並透過SingalR更新至前端,因此API站台中定時排程需求就使用短小精幹的Coravel來完成
撰寫SchedulerPageJob
因透過Coravel執行,實作IInvocable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class SchedulerPageJob : IInvocable { private readonly HttpClient _client; private readonly IHubContext<NoticeHub> _hubContext; public SchedulerPageJob(HttpClient client, IHubContext<NoticeHub> hubContext) { _client = client; _hubContext = hubContext; }
public Task Invoke() { var result = _client.GetResponse<string, MyTokenResult, List<JobInfoResponseModel>>( "", HttpAction.Post, "api/schedulerinfo/jobs", HttpStatusCode.OK , out var rtnErrorCode, out var response, tokenNeeded: false , mediaType: "application/json"); if (rtnErrorCode == HttpStatusCode.OK) { _hubContext.Clients.All.SendAsync("SchedulerInfosResult" , JsonConvert.SerializeObject(result)); } return Task.CompletedTask; } }
|
其中可以看到_client
為筆者自己寫的HttpClientExtension
,可以忽略它,筆者再為它寫一篇吧,該段就是透過HttpClient
與Scheduler站台中的API
取得資訊,_hubContext
則筆者在API站台中建立的SingalR Hub
,透過Clients.All.SendAsync
的方式Broadcast出去。
Startup.cs中設定定時器
最後於Startup中透過Coravel的設定,讓取得RecurringJobs資訊定時執行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public void ConfigureServices(IServiceCollection services) { services.AddScheduler(); }
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.ApplicationServices.UseScheduler(scheduler => { scheduler.Schedule<SchedulerPageJob>().EveryFifteenSeconds(); }).OnError((ex) => { _logger.Error($"[Coravel:Job] {ex}"); }); }
|
前端專案
筆者這邊是使用angular
開發其前端頁面,因為主要這個呈現方式看個人,筆者就不在另外貼angular
相關程式碼,筆者公司有買版型,因此算美觀了,主要概念是依照LastJobState
,CardHeader
的顏色有所不一樣
- Succeded:藍色底
- Failed:紅色底
- Processing:黃色底
- 空白則白色底
再加上若有Failed
時使用相關Toast
套件於右上角浮出,藉由達到提醒效果。
結論
透過改造Dashboard,筆者有機會看Hangfire
的原始碼,因為Document
實在是太難看,也沒辦法用F12
追看程式碼相關性,索性就直接下載原始碼來觀看一下,才知道說那些物件有甚麼屬性,關聯是甚麼,才最終以完美呈現算美觀的客製化Dashboard
,加上CronExpression
的翻譯套件,整個完美。