public void ConfigureServices(IServiceCollection services)
{
// Add Hangfire services.
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
}));
// Add the processing server as IHostedService
services.AddHangfireServer();
// Add framework services.
services.AddMvc();
}
- پکیج HangFire برای مدیریت کار و زمان ، Table هایی دارد که پس از نصب، بر روی دیتابیس برنامهی شما قرار میگیرد. فقط باید دقت داشته باشید ConnectionString دیتابیس خود را در متد AddHangFire مقدار دهی کنید، تا از این طریق دیتابیس برنامه را شناخته و Tableهای مورد نظر را در Schema جدیدی با نام HangFire به آن اضافه کند.
پ ن : HangFire بصورت پیشفرض با دیتابیس SqlServer ارتباط برقرار میکند.
- این پکیج یک داشبورد اختصاصی دارد که در آن لیستی از انواع تسکهای در صف انجام و گزارشی از انجام شدهها را در اختیار ما قرار میدهد. برای تنظیم این داشبورد باید Middleware مربوط به آن و endpoint جدیدی را برای شناسایی مسیر داشبورد HangFire در برنامه، در متد Configure کلاس Startup اضافه کنید :
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IHostingEnvironment env)
{
// HangFire Dashboard
app.UseHangfireDashboard();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
// HangFire Dashboard endpoint
endpoints.MapHangfireDashboard();
});
}
برای اینکه به داشبورد HangFire دسترسی داشته باشید، کافیست پس از نصب و انجام تنظیمات مذکور، برنامه را اجرا کنید و در انتهای Url برنامه، کلمهی "hangfire" را وارد کنید. سپس وارد پنل داشبورد آن خواهید شد.
http://localhost:50255/hangfire
البته میتوانید آدرس داشبورد HangFire را در برنامه، از کلمهی "hangfire" به هر چیزی که میخواهید شخصی سازی کنید. برای اینکار کافیست درون Middleware تعریف شده بصورت ورودی string، آدرس جدیدی را برای HangFire تعریف کنید.
app.UseHangfireDashboard("/mydashboard");
و به طبع در Url :
http://localhost:50255/mydashboard
2. داشبورد :
داشبورد HangFire شامل چندین بخش و تب مختلف است که به اختصار هر یک را بررسی خواهیم کرد.
تب Job :
همهی تسکهای تعریف شده، شامل Enqueued, Succeeded, Processing, Failed و... در این تب نشان داده میشوند.
تب Retries :
این تب مربوط به تسکهایی است که در روال زمانبندی و اجرا، به دلایل مختلفی مثل Stop شدن برنامه توسط iis یا Down شدن سرور و یا هر عامل خارجی دیگری، شکست خوردند و در زمانبندی مشخص شده، اجرا نشدند. همچنین قابلیت دوبارهی به جریان انداختن job مورد نظر را در اختیار ما قرار میدهد که از این طریق میتوان تسکهای از دست رفته را مدیریت کرد و دوباره انجام داد.
تب Recurring Jobs :
وقتی شما یک تسک را مانند گرفتن بکاپ از دیتابیس، بصورت ماهانه تعریف میکنید و قرار است در هر ماه، این اتفاق رخ دهد، این مورد یک تسک تکراری تلقی شده و این تب مسئول نشان دادن اینگونه از تسکها میباشد.
تب Servers :
این بخش، سرویسهایی را که HangFire برای محاسبهی زمانبندی از آنها استفاده میکند، نشان میدهد. وقتی متد services.AddHangfireServer را به متد ConfigureService کلاس Startup اضافه میکنید، سرویسهای HangFire جهت محاسبهی زمانبندیها فعال میشوند.
3. امنیت داشبورد :
همانطور که دانستید، داشبورد، اطلاعات کاملی از نوع کارها و زمان اجرای آنها و نام متدها را در اختیار ما قرار میدهد و همچنین اجازهی تغییراتی را مثل حذف یک تسک، یا دوباره به اجرا در آوردن تسکها و یا اجرای سریع تسکهای به موعد نرسیده را به کاربر میدهد. گاهی ممکن است این اطلاعات، شامل محتوایی امنیتی و غیر عمومی باشد که هرکسی در برنامه حق دسترسی به آنها را ندارد. برای مدیریت کردن این امر، میتوانید مراحل زیر را طی کنید :
مرحله اول : یک کلاس را ایجاد میکنیم (مثلا با نام MyAuthorizationFilter) که این کلاس از اینترفیسی با نام IDashboardAuthorizationFilter ارث بری خواهد کرد.
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
// Allow all authenticated users to see the Dashboard (potentially dangerous).
return httpContext.User.Identity.IsAuthenticated;
}
}
درون این کلاس، متدی با نام Authorize از اینترفیس مربوطه، پیاده سازی میشود که شروط احراز هویت و صدور یا عدم صدور دسترسی را کنترل میکند. این متد، یک خروجی Boolean دارد که اگر هر یک از شروط احراز هویت شما تایید نشد، خروجی false را بر میگرداند. در این مثال، ما برای دسترسی، محدودیت Login بودن را اعمال کردهایم که این را از HttpContext میگیریم.
مرحله دوم : در این مرحله کلاسی را که بعنوان فیلتر احراز هویت برای کاربران ساختهایم، در optionهای middleware پکیج HangFire اضافه میکنیم.
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new [] { new MyAuthorizationFilter() }
});
یکی دیگر از optionهای این middleware که میتوان برای کنترل دسترسی در HangFire استفاده کرد، گزینهی Read-only view نام دارد.
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
IsReadOnlyFunc = (DashboardContext context) => true
});
این گزینه اجازهی هرگونه تغییری را در روند تسکها، از طریق صفحهی داشبورد، از هر کاربری سلب میکند و داشبورد را صرفا به جهت نمایش کارها استفاده میکند نه چیز دیگر.
انواع تسکها در HangFire :
1. تسکهای Fire-And-Forget :
تسکهای Fire-And-Forget زمانبندی خاصی ندارند و بلافاصله بعد از فراخوانی، اجرا میشوند. برای مثال شرایطی را در نظر بگیرید که میخواهید پس از ثبت نام هر کاربر در وبسایت، یک ایمیل خوش آمد گویی ارسال کنید. این عمل یک تسک پس زمینه تلقی میشود، اما زمانبندی خاصی را نیز نمیخواهید برایش در نظر بگیرید. در چنین شرایطی میتوانید از متد Enqueue استفاده کنید و یک تسک Fire-And-Forget را ایجاد کنید تا این تسک صرفا در تسکهای پس زمینهتان نام برده شود و قابل مشاهده باشد.
public class HomeController : Controller
{
private readonly IBackgroundJobClient _backgroundJobClient;
public HomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = backgroundJobClient;
}
[HttpPost]
[Route("welcome")]
public IActionResult Welcome(string userName)
{
var jobId = _backgroundJobClient.Enqueue(() => SendWelcomeMail(userName));
return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
}
public void SendWelcomeMail(string userName)
{
//Logic to Mail the user
Console.WriteLine($"Welcome to our application, {userName}");
}
}
همانطور که میبینید در مثال بالا ابتدا برای استفاده از تسکهای Fire-and-Forget در HangFire باید اینترفیس IBackgroundJobClient را تزریق کنیم و با استفاده از متد Enqueue در این اینترفیس، یک تسک پس زمینه را ایجاد میکنیم که کار آن، فراخوانی متد SendWelcomeMail خواهد بود.
2. تسکهای Delayed :
همانطور که از اسم آن پیداست، تسکهای Delayed، تسکهایی هستند که با یک تاخیر در زمان، اجرا خواهند شد. بطور کلی زمانبندی این تسکها به دو دسته تقسیم میشود :
- دسته اول : اجرا پس از تاخیر در زمانی مشخص.
همان شرایط ارسال ایمیل را به کاربرانی که در وبسایتتان ثبت نام میکنند، در نظر بگیرید؛ اما اینبار میخواهید نه بلافاصله، بلکه 10 دقیقه بعد از ثبت نام کاربر، ایمیل خوش آمد گویی را ارسال کنید. در این نوع شما یک تاخیر 10 دقیقهای میخواهید که Delayed Jobها اینکار را برای ما انجام میدهند.
public class HomeController : Controller
{
private readonly IBackgroundJobClient _backgroundJobClient;
public HomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = backgroundJobClient;
}
[HttpPost]
[Route("welcome")]
public IActionResult Welcome(string userName)
{
var jobId = BackgroundJob.Schedule(() => SendWelcomeMail(userName),TimeSpan.FromMinutes(10));
return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
}
public void SendWelcomeMail(string userName)
{
//Logic to Mail the user
Console.WriteLine($"Welcome to our application, {userName}");
}
}
در این مثال با استفاده از متد Schedule در اینترفیس IBackgroundJobClient توانستیم متد SendWelcomeMail را صدا بزنیم و با ورودی TimeSpan یک تاخیر 10 دقیقهای را در متد HangFire اعمال کنیم.
همچنین میتوانید از ورودیهای دیگر نوع TimeSpan شامل TimeSpan.FromMilliseconds و TimeSpan.FromSecondsو TimeSpan.FromMinutes و TimeSpan.FromDays برای تنظیم تاخیر در تسکهای خود استفاده کنید.
- دسته دوم : اجرا در زمانی مشخص.
نوع دیگر استفاده از متد Schedule، تنظیم یک تاریخ و زمان مشخصی برای اجرا شدن تسکهای در آن تاریخ و زمان واحد میباشد. برای مثال سناریویی را در نظر بگیرید که دستور اجرا و زمانبندی آن، در اختیار کاربر باشد و کاربر بخواهد یک Reminder، در تاریخ مشخصی برایش ارسال شود که در اینصوررت میتوانید با استفاده از instance دیگری از متد Schedule که ورودی ای از جنس DateTimeOffset را دریافت میکند، تاریخ مشخصی را برای آن اجرا، انتخاب کنید.
public class HomeController : Controller
{
private readonly IBackgroundJobClient _backgroundJobClient;
public HomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = IBackgroundJobClient;
}
[HttpPost]
[Route("welcome")]
public IActionResult Welcome(string userName , DateTime dateAndTime)
{
var jobId = BackgroundJob.Schedule(() => SendWelcomeMail(userName),DateTimeOffset(dateAndTime));
return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
}
public void SendWelcomeMail(string userName)
{
//Logic to Mail the user
Console.WriteLine($"Welcome to our application, {userName}");
}
}
در این مثال، تاریخ مشخصی را برای اجرای تسکهای خود، از کاربر، در ورودی اکشن متد دریافت کردهایم و به متد Schedule، در غالب DateTimeOffset تعریف شده، پاس میدهیم.
3. تسکهای Recurring :
تسکهای Recurring به تسکهایی گفته میشود که باید در یک بازهی گردشی از زمان اجرا شوند. در یک مثال، بیشتر با آن آشنا خواهیم شد. فرض کنید میخواهید هر هفته، برنامه از اطلاعات دیتابیس موجود بکاپ بگیرد. در اینجا تسکی را دارید که قرار است هر هفته و هربار به تکرر اجرا شود.
public class HomeController : Controller
{
private readonly IRecurringJobManager _recurringJobManager;
public HomeController(IRecurringJobManager recurringJobManager)
{
_recurringJobManager = recurringJobManager;
}
[HttpPost]
[Route("BackUp")]
public IActionResult BackUp(string userName)
{
_recurringJobManager.AddOrUpdate("test", () => BackUpDataBase(), Cron.Weekly);
return Ok();
}
public void BackUpDataBase()
{
// ...
}
}
برای تنظیم یک Recurring Job باید اینترفیس دیگری را بنام IRecurringJobManager، تزریق کرده و متد AddOrUpdate را استفاده کنید. در ورودی این متد، یک نوع تعریف شده در HangFire بنام Cron دریافت میشود که بازهی گردش در زمان را دریافت میکند که در اینجا بصورت هفتگی است.
انواع دیگر Cron شامل :
- هر دقیقه (Cron.Minutely) :
این Cron هر دقیقه یکبار اجرا خواهد شد.
_recurringJobManager.AddOrUpdate("test", () => job , Cron.Minutely);
این Cron هر یک ساعت یکبار و بصورت پیشفرض در دقیقه اول هر ساعت اجرا میشود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly);
اما میتوانید یک ورودی دقیقه به آن بدهید که در اینصورت در N اُمین دقیقه از هر ساعت اجرا شود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly(10));
این Cron بصورت روزانه و در حالت پیشفرض، در اولین ساعت و اولین دقیقهی هر روز اجرا خواهد شد.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily);
در حالتی دیگر میتوانید ورودی ساعت و دقیقه را به آن بدهید تا در ساعت و دقیقهای مشخص، در هر روز اجرا شود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily(3,10));
این Cron هفتگی است. بصورت پیشفرض هر هفته، شنبه در اولین ساعت و در اولین دقیقه، اجرا میشود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Weekly);
در حالتی دیگر چندمین روز هفته و ساعت و دقیقه مشخصی را در ورودی میگیرد و حول آن میچرخد.
_recurringJobManager.AddOrUpdate("test", () => Job,Cron.Weekly(DayOfWeek.Monday,3,10));
این Cron بصورت ماهانه اولین روز ماه در اولین ساعت روز و در اولین دقیقه ساعت، زمانبندی خود را اعمال میکند.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly);
و در صورت دادن ورودی میتوانید زمانبندی آن در چندمین روز ماه، در چه ساعت و دقیقهای را نیز تنظیم کنید.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly(10,3,10));
و در نهایت این Cron بصورت سالانه و در اولین ماه، روز، ساعت و دقیقه هر سال، وظیفه خود را انجام خواهد داد.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Yearly);
که اینهم مانند بقیه، ورودیهایی دریافت میکند که به ترتیب شامل ماه، روز، ساعت و دقیقه است.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Yearly(2,4,3,10));
در نهایت با استفاده از این Cronها میتوانید انواع مختلفی از Recurring Jobها را بسازید.
4. تسکهای Continuations :
این نوع از تسکها، وابسته به تسکهای دیگری هستند و بطور کلی وقتی استفاده میشوند که ما میخواهیم تسکی را پس از تسک دیگری، با یک زمانبندی، به نسبت زمان اجرای تسک اول، اجرا کنیم. برای مثال میخواهیم 10 دقیقه بعد از ثبت نام کاربر، برای او ایمیل احراز هویت ارسال شود که شبیه اینکار را در تسکهای Delayed انجام داده بودیم. اما همچنین قصد داریم 5 دقیقه بعد از ارسال ایمیل احراز هویت، لینک فرستاده شده را منسوخ کنیم. در این سناریو ما دو زمانبندی داریم؛ اول 10 دقیقه بعد از ثبت نام کاربر و دوم 5 دقیقه بعد از اجرای متد اول.
var stepOne = _backgroundJobClient.Schedule(() => SendAuthorizationEmail(), TimeSpan.FromMinute(10));
_backgroundJobClient.ContinueJobWith(stepOne, () => _backgroundJobClient.Schedule(() => ExpireAuthorizationEmail(), TimeSpan.FromHours(5)));
برای ایجاد یک Continuations Job باید از متد ContinueJobWith در اینترفیس IBackgroundJobClient استفاده کنیم و در ورودی اول این متد، آیدی تسک ایجاد شده قبلی را پاس دهیم.
برخی از نکات و ترفندهای HangFire
1. استفاده از Cron Expression در Recurring Jobها :
بطور کلی، Cron، ساختاری تعریف شده برای تعیین بازههای زمانی است. Cron اختصار یافتهی کلمات Command Run On میباشد که به اجرا شدن یک دستور، در زمان مشخصی اشاره دارد. برای استفاده از آن، ابتدا به تعریف این ساختار میپردازیم :
* * * * * command to be executed
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)
این ساختار را از پایین به بالا در زیر برایتان تشریح میکنیم :
- فیلد اول (Minute) : در این فیلد باید دقیقهای مشخص از یک ساعت را وارد کنید؛ مانند دقیقه 10 (میتوانید محدودهای را هم تعیین کنید)
- فیلد دوم (Hour) : در این فیلد باید زمان معلومی را با فرمت ساعت وارد کنید؛ مانند ساعت 7 (میتوانید محدودهای را هم تعیین کنید، مانند ساعات 12-7)
- فیلد سوم (Day of Month) : در این فیلد باید یک روز از ماه را وارد کنید؛ مانند روز 15 ام از ماه (میتوانید محدودهای را هم تعیین کنید)
- فیلد چهارم (Month) : در این فیلد باید یک ماه از سال را وارد کنید؛ مثلا ماه 4 ام(آوریل) (میتوانید محدودهای را هم تعیین کنید)
- فیلد پنجم (Day of Week) : در این فیلد باید روزی از روزهای هفته یا محدودهای از آن روزها را تعیین کنید. مانند صفرم هفته که در کشورهای اروپایی و آمریکایی معادل روز یکشنبه است.
همانطور که میبینید، Cronها دسترسی بهتری از تعیین بازههای زمانی مختلف را ارائه میدهند که میتوانید از آن در Recurring متدها بجای ورودیهای Yearly - Monthly - Weekly - Daily - Hourly - Minutely استفاده کنید. در واقع خود این ورودیها نیز متدی تعریف شده در کلاس Cron هستند که با فراخوانی آن، خروجی Cron Expression را میسازند و در درون ورودی متد Recurring قرار میگیرند.
در ادامه مثالی را خواهیم زد تا نیازمندی به Cron Expressionها را بیشتر درک کنید. فرض کنید میخواهید یک زمانبندی داشته باشید که "هر ماه بین بازه 10 ام تا 15 ام، بطور روزانه در ساعت 4:00" اجرا شود. اعمال این زمانبندی با متدهای معمول در کلاس Cron امکان پذیر نیست؛ اما میتوانید با Cron Expression آنرا اعمال کنید که به این شکل خواهد بود:
برای ساخت Cron Expressionها وبسایت هایی وجود دارند که کمک میکنند انواع Cron Expressionهای پیچیدهای را طراحی کنیم و با استفاده از آن، زمانبندیهای دقیقتر و جزئیتری را بسازیم. یکی از بهترین وبسایتها برای اینکار
crontab.guru است.
پ. ن. برای استفاده از Cron Expression در متدهای Recurring کافی است بجای ورودیهای Yearly - Monthly - Weekly - Daily - Hourly - Minutely ، خود Cron Expression را درون ورودی متد تعریف کنیم :
_recurringJobManager.AddOrUpdate("test", () => job , "0 4 10-15 * *" );
2. متد Trigger :
متد Trigger یک متد برای اجرای آنی تسکهای Recurring است که به کمک آن میتوانید این نوع از تسکها را بدون در نظر گرفتن زمانبندی آنها، در لحظه اجرا کنید و البته تاثیری در دفعات بعدی تکرار نداشته باشد.
RecurringJob.Trigger("some-id");
3. تعیین تاریخ انقضاء برای Recurring Jobها :
گاهی ممکن است در تسکهای Recurring شرایطی پیش آید که برفرض میخواهید کاری را هر ماه انجام دهید، اما این تکرار در پایان همان سال تمام میشود. در اینصورت باید یک Expire Time برای متد Recurring خود تنظیم کنیم تا بعد از 12 ماه تکرار، در تاریخ 140X/12/30 به پایان برسد. HangFire برای متدهای Recurring ورودی با عنوان ExpireTime تعریف نکرده، اما میتوان از طریق ایجاد یک زمانبندی، تاریخ مشخصی را برای حذف کردن متد Recurring تعریف کرد که همانند یک ExpireTime عمل میکند.
_recurringJobManager.AddOrUpdate("test", () => Console.WriteLine("Recurring Job"), Cron.Monthly);
_backgroundJobClient.Schedule(() => _recurringJobManager.RemoveIfExists("test"), DateTimeOffset(dateAndTime));
با اجرای این متد، اول کاری برای تکرار در زمانبندی ماهیانه ایجاد میشود و در متد دوم، زمانی برای حذف متد اول مشخص میکند.
در آخر امیدوارم این مقاله برایتان مفید واقع شده باشد. میتوانید فیدبکتان را در قالب کامنت یا یک
قهوه برایم ارسال کنید.