[.Net Core WebApi] วิธีการตั้งเวลาให้ function หรือ jobs ทำงาน ด้วย Hangfire แบบ Step By Step

Chaiyarin Niamsuwan
odds.team
Published in
6 min readApr 5, 2020

คุณเคยพบปัญหาเหล่านี้ไหมครับ อยากให้ function ของเรา ทำงานทุกๆ เที่ยงคืน อยากให้ function ของเรา ทำงานทุกๆชั่วโมง อยากให้ function ของเรา ทำงานตามเวลาที่เราต้องการ

Cron Job

จริงๆแล้ว มันก็มีหลายวิธีที่ทำได้ เช่น เขียน Script ตามภาษาที่ถนัด มา 1 ตัว และเขียน CronJob ให้ Run ตามเวลาที่เรากำหนด

Jenkins

หรือทำ API มาซักเส้นสำหรับ Function ที่เราอยากให้ Run แล้วสั่งให้ Jenkins Call API ตัวนั้น ตาม เวลาที่เราตั้งไว้ใน Jenkins ก็ได้อีกเหมือนกัน

แต่วันนี้ผมจะเสนออีก 1 วิธี ที่ทำได้ง่ายๆ ทำได้ใน Project ของตัว .Net Core WebApi ของเราโดยไม่ต้องเขียน Script แยกออกมาให้วุ่นวาย แถมยังมี Dashboard สวยๆ ให้เราได้ดูว่า function หรือ Job ของเราทำงาน สำเร็จ(success) หรือ ไม่สำเร็จ(fail) อีกด้วย

มาเริ่มต้นกันเลย

Step 0) HangFire คือ อะไร

HangFire คือ Open Source Framework ตัวนึงของ .Net และ .Net Core ที่มาคอยช่วยเราจัดการ Background Process Job ต่างๆของเรา ให้ง่ายขึ้น โดยมี Feature หลักในการจัดการ Job อยู่ 6 ตัวด้วยกัน

  1. Fire And Forget
  2. Delayed
  3. Recurring
  4. Continuations
  5. Batches
  6. Batch Continuations

เดี๋ยว Feature เหล่านี้จะไปอธิบายวิธีใช้อีกทีด่านล่าง

Monitoring UI Dashboard

โดยใน Framework ได้มี Build-in Dashboard สวยๆ ให้เรา Monitoring Job ต่างๆที่เราสร้างขึ้น ว่า Process มันทำไหม สำเร็จหรือไม่ สามารถเขียนให้มันแจ้งเตือนเราเมื่อมันทำไม่สำเร็จก็ย่อมได้

รองรับการบันทึก Job จากหลากหลายเจ้า

และ Hangfire ยังรองรับการเก็บ Job ในฐานข้อมูลหลายเจ้า เพื่อให้ Make Sure ว่าเมื่อต่อให้ Restart Project .Net ของเราไปแล้ว Job ก็ยังคงทำงานได้อยู่เหมือนเดิม

และอีกหนึ่งจุดพีค เราไม่จำเป็นจะต้องไปสร้าง Windows Scheduler หรือใช้ Windows Service ใด ด้วยครับ

Step 1) สร้าง Project .Net Core WebApi ง่ายๆ มาซัก 1 ตัว แบบนี้

dotnet new webapi -o hangfireDemo

เมื่อ run คำสั่งด้านบนแล้วจะได้ผลลัพธ์แบบนี้

สร้างโปรเจค .Net Core WebApi แบบง่ายๆ ชื่อโปรเจคว่า hangfireDemo

Step 2) เลือก Editor ที่ชอบแล้วเปิดโครงสร้าง Source Code ของโปรเจคขึ้นมาจะได้หน้าตาแบบนี้

หน้าตาโครงสร้าง .Net Core หลังจากสร้างโปรเจคเสร็จ

Step 3) ทีนี้เรามาลองทดสอบกันก่อนว่า Project .Net Core WebApi ที่พึ่งสร้างไปเมื่อกี้นั้น ทำงานได้ ทดสอบดังนี้

เปิด Terminal แล้วเข้ามาให้อยู่ ภายใต้ Directory ชื่อ hangfireDemo แล้วพิมพ์ Command ดังนี้

dotnet run

ผลลัพธ์ที่ได้ควรจะเป็นแบบนี้ดังภาพ

ผลลัพธ์จากการ Run ‘dotnet run’

Step 4) ทดลองยิง API เพื่อทดสอบว่ามันทำงานได้จริงนะ เปิด Browser ขึ้นมา แล้วใส่ URL :

[สำหรับ .Net Core 3]
URL :
https://localhost:5001/WeatherForecast/
[สำหรับ .Net Core 2]
URL :
https://localhost:5001/api/Values/

หลังจากเปิดเข้าไปใน URL แล้วจะได้ Response เป็น JSON กลับมา ค่าของการ Response อาจจะไม่เหมือนกัน ตาม Version ของ .Net Core แต่โดยรวม ถ้ามีอะไร Response มา แปลว่า ใช้งานได้สมบูรณ์แล้ว

Response หน้าตาของ .Net Core 3 จะได้แบบนี้

Step 5) มาแล้วครับ ตรงนี้หละเราจะเริ่ม ติดตั้ง Package Hangfire กันละ โดยเริ่มจาก ใช้คำสั่งนี้ใน Terminal ภายใต้ Directory hangFireDemo ในการติดตั้ง

dotnet add package HangFire --version 1.7.10

เมื่อ Run คำสั่งนี้ ผลลัพธ์ที่ได้จะเป็นแบบนี้

จังหวะนี้ตัว .Net Core จะติดตั้ง Package สำเร็จเรียบร้อยแล้ว

เพื่อตรวจสอบว่าติดตั้งเรียบร้อยแล้วจริงๆ ให้เข้าไปดูที่ไฟล์ hangfireDemo.csproj

จะเห็นว่ามีบรรทัดนี้ เพิ่มเข้ามาในไฟล์ hangfireDemo.csproj

Step 6) เปิดไฟล์ชื่อ Startup.cs ขึ้นมา จะเห็น Source Code ลักษณะดังนี้

Source Code แบบ Default ในไฟล์ Startup.cs

Step 6.1) สิ่งที่เราต้องทำถัดมาก็คือ เพิ่ม Source Code ไม่เกิน 6 บรรทัดตามผมดังนี้

เพิ่ม 3 บรรทัดนี้ใน Method : ConfigureServices
[เพื่ม Source Code ด้านล่างนี้ใน Method : ConfigureServies ไฟล์ Startup.cs]services.AddHangfire(config => {      config.UseSqlServerStorage("Server=tcp:localhost,1433;Database=HangfireDemo;User Id=sa;Password=p@ssw0rd;"); });   ---> (1)services.AddHangfireServer();  ---> (2)

อธิบาย Source Code :

(1) : ช่วงที่ 1 นั้น HangFire ต้อง ConnectionString เพื่อใช้เก็บในฐานข้อมูล Job ต่างๆที่เราต้องการจะ Runโดยใน Example ที่ผมจัดทำนี้เราจะใช้ฐานข้อมูล SQLServer ไม่ใช่ MySQL นะครับ แต่เป็น SQLServer

จริงๆแล้ว HangFire สามารถใช้ฐานข้อมูลอื่นได้ เช่น Redis ก็ทำได้เช่นกัน หรือ SQL Azure ก็ได้

เพื่อให้เพื่อนๆใช้งาน Source Code ในตัวอย่างนี้ได้ทันที ผมได้เตรียม Command ในการ Run Docker มาให้ ถ้าเพื่อนๆมี Docker อยู่แล้ว Copy คำสั่งนี้ไปแปะได้เลย

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=p@ssw0rd' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latestเมื่อ image run แล้วอย่าลืมไปสร้าง Dabatabase : HangfireDemo ในฐานข้อมูลด้วยนะครับ

(2) : HangFire จะ Run Server เล็กๆขึ้นมาเอาไว้จัดการคิวของ Function ที่เราตั้งไว้ หรือ Job ที่เราตั้งไว้ให้ทำงานได้

ต่อมาครับเพิ่ม Code อีกเล็กน้อยตามภาพ

ใน Method : Configure ให้เพื่อ Parameter 1 ตัวชื่อ IBackgroundJobClient ในไฟล์ Startup.cs

ต่อมาเพิ่ม อีก 1 บรรทัดใน Method : Configure

[อธิบาย Souce Code ด้านล่างนี้ใน Method : Configure]public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
IBackgroundJobClient backgroundJobs คือ การ Inject BackgroundJob ของ HangFire มาใช้งานbackgroundJobs.Enqueue คือ การ Execute Job แรกที่เราต้องการให้ทำงานทันทีหลังจาก Run .Net Core ขึ้นมา ณ ที่นี้ถ้าพวกเราทำสำเร็จ ใน Log จะพ่น คำว่า "Hello world from Hangfire!" ออกมา

สรุปหน้าตา Source Code โดยรวมหลังจากปรับแก้แล้ว จะได้ประมาณนี้

[หน้าตา Source Code ในไฟล์ Startup.cs]using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace hangfireDemo {
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration {
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddControllers();
services.AddHangfire(config => {
config.UseSqlServerStorage("Server=tcp:localhost,1433;Database=HangfireDemo;User Id=sa;Password=p@ssw0rd;");
});
services.AddHangfireServer();

}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();app.UseRouting();app.UseAuthorization();backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
}

Step 7) สั่ง dotnet run อีกครั้ง ถ้าได้หน้าตาแบบนี้ แปลว่า HangFire คุณพร้อมใช้งานแล้ว

จะสังเกตว่าหลังจาก dotnet run แล้วจะมีคำว่า Hello world from Hangfire! ขึ้นมาแล้วครับผม

ถ้ามีคำว่า Hello world from Hangfire! ขึ้นมาใน Console แปลว่า HireFire คุณพร้อมใช้งานแล้วครับ

Step 8) ลองไปดู Database HangFireDemo ที่เราสร้างไว้เมื่อกี้ก็จะมี Data เกิดขึ้นมาแล้ว

ในภาพจะเห็นว่า Hello word from Hangfire ได้ถูกทำงาน จริงๆ และ success เรียบร้อย
Zoom ให้เห็นอีกทีชัดๆ ในฐานข้อมูล HangFireDemo

Step 9) ทีนี้เราลองมาทำความรู้จักอีกซัก 2 3 คำสั่งในการ ตั้งเวลาให้ Function หรือ Method ทำงาน กันอีกซักเล็กน้อย

9.1) Fire-and-forget jobs

คำสั่งนี้สิ่งที่มันทำก็คือ มันจะทำการ Run Function ที่เราต้องการ Trigger ทันทีเลย และจะไม่กลับมาทำอีกครั้งด้วย ทำทีเดียวเลิกแล้วต่อกันเลย และคำสั่งนี้เราก็ได้ใช้ไปในตัวอย่างเรียบร้อย

var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));

9.2) Delayed jobs

คำสั่งนี้ จะทำให้เราสามารถ Trigger Function หรือ Method ของเรา หลังจากนี้อีก 7 วันข้างหน้า หรือ 3 วันข้างหน้า แล้วแต่เรากำหนด

ยกตัวอย่างเช่น ระบบเราทำการแจกคูปองให้ลูกค้า และถ้าลูกค้าไม่ใช้ภายใน 7 วัน ระบบจะทำการ Call Function ไป Update ฐานข้อมูลว่า คูปองของลูกค้าใบนี้ หมดอายุแล้วนะ

var jobId = BackgroundJob.Schedule(
() => Console.WriteLine("Delayed!"),
TimeSpan.FromDays(7));

9.3) Recurring jobs

อันนี้ทีเด็ดเลย คำสั่งนี้จะทำให้เรา ทำ Function หรือ Method ที่เราจะทำการ Trigger ทุกๆ นาที หรือ ทุกๆ ชั่วโมง หรือ ทุกๆเที่ยงคืนของทุกวัน ได้

RecurringJob.AddOrUpdate(
() => Console.WriteLine("Recurring!"),
Cron.Daily); // Daily, Minutely, Hourly, Weekly

9.4) Continuations

คำสั่งนี้เป็นคำสั่งสำหรับ เมื่อ Job ที่เราเคยสั่งไว้ไม่ว่าจะเป็น Delayjob หรือ RecurringJob พวกนี้ทำงานเสร็จหมดแล้ว เราจะให้มันไป Run Function อะไรต่อ

เช่น หลังจาก คูปองลูกค้าหมด อายุ ให้ไปทำ Update Generate Report รอไว้ให้หัวหน้าดูนะ อะไรทำนองนี้ โดยอ้างอิงจาก JobId

BackgroundJob.ContinueJobWith(
jobId,
() => Console.WriteLine("Continuation!"));

Step 10) มาลองใช้ Recurring jobs กันดูอีกนิด

สมมติว่าเราต้องการให้ Function เรา Run ทุก 1 นาที เลยนะ เราจะต้องทำแบบนี้มาดูกันครับ

Step 10.1) เปิดไฟล์ Startup.cs แล้วเพิ่ม Code ตรงนี้ลงไปที่ Method: Configure

[เพิ่ม Code ตรงนี้ในไฟล์ Startup.cs หลัง backgroundJobs.Enqueue]RecurringJob.AddOrUpdate(
() => Console.WriteLine($ "-- ทดสอบ Recurring ทุกๆ 1 นาที --"),
Cron.Minutely);

เมื่อเติม Code แล้ว จะได้แบบดังภาพ แบบนี้

เมื่อเติม Code แล้วจะได้แบบดังภาพ บรรทัด 53–55

Step 10.2) ลอง สั่ง dotnet run ใหม่อีกครั้งแล้วสังเกตผลลัพธ์

สังเกตตรงวงกลมสีแดง นี่คือผลลัพธ์ในการ ตั้งเวลา Trigger function ทุก 1 นาที

สรุปเอาเป็นว่า บทความนี้ ลองเอาไปใช้กันก่อนนะครับเพื่อเป็นประโยชน์ต่อเพื่อนๆ ในการหาวิธีตั้งเวลา Trigger Method ต่างๆ ด้วย .Net Core WebApi

Step 11) หลังจากที่เราสามารถตั้งเวลาใน Job ของเราได้แล้ว ถัดมาจะเป็นการเรียกใช้งาน Dashboard ของ HangFire เพื่อให้เราดู Log การทำงานของ Job เราได้ง่ายขึ้น มีวิธีการดังนี้

[เพิ่ม 1 บรรทัดด่านล่างในไฟล์ Startup.cs ภายใน Method : Configure()]app.UseHangfireDashboard();

พอเพิ่มแล้วก็จะได้ไฟล์ Startup.cs หน้าตาประมาณนี้

หน้าตาไฟล์หลังจากเพิ่มเสร็จแล้ว

หลังจากนั้นลอง สั่ง dotnet run อีกครั้ง แล้วเข้าไปที่ URL นี้

https://localhost:5001/hangfire/

เราก็จะได้หน้าตา Dashboard สวยๆ เอาไว้ดู Log ของ Job ต่างๆที่เราตั้งเวลา หรือ กำหนด Delay ไว้แล้วแบบนี้

หน้าตา Dashboard Hangfire

จบแล้วครับ ไว้บทความหน้าผมจะมาอธิบายวิธีการใช้งาน Dashboard ของ Hangfire และ วิธีการทำ Authentication เพื่อให้เมื่อเรา Deploy Hangfire ขึ้น Production แล้วเราไม่อยากให้ใครเข้ามาดู Dashboard ของเราตรงๆ ต้องทำอย่างไร

ลองเอาไปเล่นดูนะครับแล้วเจอกันใหม่บทความหน้า

อ้างอิง : https://www.hangfire.io/

--

--