تسکهای پس زمینه (Background Job) چیست؟
بطور کلی تسکهای پس زمینه، کارهایی هستند که برنامه باید بصورت خودکار در زمانهای مشخصی آنها را انجام دهد؛ برای مثال شرایطی را در نظر بگیرید که متدی را با حجم زیادی از محاسبات پیچیده دارید که وقتی کاربر درخواست خود را ارسال میکند، شروع به محاسبه میشود و کاربر چارهای جز انتظار نخواهد داشت؛ اما اگر اینکار در زمانی دیگر، قبل از درخواست کاربر محاسبه میشد و صرفا نتیجهاش به کاربر نمایش داده میشد، قطعا تصمیم گیری بهتری نسبت به محاسبهی آنی آن متد، در زمان درخواست کاربر بوده.
در سناریوی دیگری تصور کنید میخواهید هر شب در ساعتی مشخص، خلاصهای از مطالب وبسایتتان را برای کاربران وبسایت ایمیل کنید. در این حالت برنامه باید هر شب در ساعتی خاص اینکار را برای ما انجام دهد و تماما باید این اتفاق بدون دخالت هیچ ارادهی انسانی و بصورت خودکار توسط برنامه انجام گیرد.
همچنین شرایطی از این قبیل، ارسال ایمیل تایید هویت، یک ساعت بعد از ثبت نام، گرفتن بک آپ از اطلاعات برنامه بصورت هفتگی و دیگر این موارد، همه در دستهی تسکهای پس زمینه (Background Job) از یک برنامه قرار دارند.
سؤال : HangFire چیست؟
همانطور که دانستید، تسکهای پس زمینه نیاز به یک سیستم مدیریت زمان دارند که کارها را در زمانهای مشخص شدهای به انجام برساند. HangFire یک پکیج متن باز، برای ایجاد سیستم زمانبندی شدهی کارها است و اینکار را به سادهترین روش، انجام خواهد داد. همچنین HangFire در کنار Quartz که سیستم دیگری جهت پیاده سازی زمانبندی است، از معروفترین پکیجها برای زمانبندی تسکهای پس زمینه بشمار میروند که در ادامه بیشتر به مزایا و معایب این دو میپردازیم.
مقایسه HangFire و Quartz
میتوان گفت این دو پکیج تا حد زیادی شبیه به هم هستند و تفاوت اصلی آنها، در لایههای زیرین و نوع محاسبات زمانی هریک، نهفته است که الگوریتم مختص به خود را برای این محاسبات دارند؛ اما در نهایت یک کار را انجام میدهند.
دیتابیس :
تفاوتی که میتوان از آن نام برد، وجود قابلیت Redis Store در HangFire است که Quratz چنین قابلیتی را از سمت خودش ارائه نداده و برای استفاده از Redis در Quartz باید شخصا این دو را باهم کانفیگ کنید. دیتابیس Redis بخاطر ساختار دیتابیسی که دارد، سرعت و پرفرمنس بالاتری را ارائه میدهد که استفاده از این قابلیت، در پروژههایی با تعداد تسکها و رکوردهای زیاد، کاملا مشهود است. البته این ویژگی در HangFire رایگان نیست و برای داشتن آن از سمت HangFire، لازم است هزینهی آن را نیز پرداخت کنید؛ اما اگر هم نمیخواهید پولی بابتش بپردازید و همچنان از آن استفاده کنید، یک پکیج سورس باز برای آن نیز طراحی شده که از این لینک میتوانید آنرا مشاهده کنید.
ساختار :
پکیج HangFire از ابتدا با دات نت و معماریهای دات نتی توسعه داده شده، اما Quartz ابتدا برای زبان جاوا نوشته شده بود و به نوعی از این زبان، ریلیزی برای دات نت تهیه شد و این موضوع طبعا تاثیرات خودش را داشته و برخی از معماریها و تفکرات جاوایی در آن مشهود است که البته مشکلی را ایجاد نمیکند و محدودیتی نسبت به HangFire از لحاظ کارکرد، دارا نیست. شاید تنها چیزی که میتوان در این باب گفت، DotNet Friendlyتر بودن HangFire است که کار با متدهای آن، آسانتر و به اصطلاح، خوش دستتر است.
داشبورد :
هردو پکیج از داشبورد، پشتیبانی میکنند که میتوانید در این داشبورد و ui اختصاصی که برای نمایش تسکها طراحی شده، تسکهای ایجاد شده را مدیریت کنید. داشبورد HangFire بصورت پیشفرض همراه با آن قرار دارد که بعد از نصب HangFire میتوانید براحتی داشبورد سوار بر آن را نیز مشاهده کنید. اما در Quartz ، داشبورد باید بصورت یک Extension، در پکیجی جدا به آن اضافه شود و مورد استفاده قرار گیرد. در لینکهای 1 و 2، دوتا از بهترین داشبوردها برای Quartz را مشاهده میکنید که در صورت نیاز میتوانید از آنها استفاده کنید.
استفاده از HangFire
1. نصب :
- برای نصب HangFire در یک پروژهی Asp.Net Core لازم است ابتدا پکیجهای مورد نیاز آن را نصب کنید؛ که شامل موارد زیر است:
Install-Package Hangfire.Core Install-Package Hangfire.SqlServer Install-Package Hangfire.AspNetCore
- پس از نصب پکیجها باید تنظیمات مورد نیاز برای پیاده سازی HangFire را در برنامه، اعمال کنیم. این تنظیمات شامل افزودن سرویسها و اینترفیسهای HangFire به برنامه است که اینکار را با افزودن HangFire به متد ConfigureService کلاس Startup انجام خواهیم داد:
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
app.UseHangfireDashboard("/mydashboard");
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() } });
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}"); } }
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}"); } }
همچنین میتوانید از ورودیهای دیگر نوع 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.Hourly) :
این Cron هر یک ساعت یکبار و بصورت پیشفرض در دقیقه اول هر ساعت اجرا میشود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly);
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly(10));
- هر روز (Cron.Daily) :
این Cron بصورت روزانه و در حالت پیشفرض، در اولین ساعت و اولین دقیقهی هر روز اجرا خواهد شد.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily);
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily(3,10));
- هر هفته (Cron.Weekly) :
این Cron هفتگی است. بصورت پیشفرض هر هفته، شنبه در اولین ساعت و در اولین دقیقه، اجرا میشود.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Weekly);
_recurringJobManager.AddOrUpdate("test", () => Job,Cron.Weekly(DayOfWeek.Monday,3,10));
- هر ماه (Cron.Monthly) :
این Cron بصورت ماهانه اولین روز ماه در اولین ساعت روز و در اولین دقیقه ساعت، زمانبندی خود را اعمال میکند.
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly);
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly(10,3,10));
- هر سال (Cron.Yearly) :
و در نهایت این 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 آنرا اعمال کنید که به این شکل خواهد بود:
0 4 10-15 * *
برای ساخت 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));
با اجرای این متد، اول کاری برای تکرار در زمانبندی ماهیانه ایجاد میشود و در متد دوم، زمانی برای حذف متد اول مشخص میکند.
در آخر امیدوارم این مقاله برایتان مفید واقع شده باشد. میتوانید فیدبکتان را در قالب کامنت یا یک قهوه برایم ارسال کنید.
تاریخ را با این فرمت به سمت سرور ارسال کنید:
const date = moment.from(this.dateControl, "en").utc(true).toJSON();
نظرات مطالب
finally در سی شارپ چه زمانی اجرا نمیشود؟
من زیادی آماتورم ولی با علیرضا موافق هستم. شما سیستم رو دور زدی، پیچوندی!! Finally باید برای کد نویسی استاندارد تست بشه.
نظرات اشتراکها
آیا یک توسعه دهندهی وب میتواند یک طراح وب هم باشد؟
به نظر تعبیر عبارت "طراح وب" طی سالهای اخیر دست خوش تغییراتی شده است و امروزه برای بسیاری از ما بخوبی روشن و واضح نیست.
من معتقدم برداشت شما از عبارت طراح وب در مطلب فوق، باید به دو شکل مجزا و متمایز تعریف شود. اول طراح گرافیک و دوم طراحی رابط کاربری. در طراحی گرافیک هنر و شناخت مخاطب در خلق یک اثر هنری حرف اول و آخر را میزند. لطفاً توجه داشته باشید که عبارت شناخت به مسائل مختلفی اشاره دارد. دوم، طراح رابط کاربری. طراحی رابط کاربری موضوع بسیار حساسی است. در یک طراحی رابط کاربری کمتر صحبت از خلق یک اثر هنری است. من معتقدم آن دوران که از وب سایت به عنوان ارائه و تبلیغات استفاده میشد رو به پایان است. در واقع این نوع نگرش به وب سایت موجب استفاده حداقلی از ظرفیت آن است.
لذا امروزه شاهد این داستان هستیم که ضمن خلاقیت، بیشتر تمرکز طراحان وب (برنامههای کاربردی وب و وب سایت ها) بروی کاربرد پذیری و رقم زدن تجربه مناسبی برای کاربر از کار کردن با برنامه است. در این راستا رابط گرافیکی کاربر تنها بخش کوچکی از فاکتورهایی است که تجارب کاربری را شکل میدهد.
من منکر بحث خلاقیت و هنر نیستم اما باید پذیرفت که ما تابع قدرتهای برتر اینترنت هستیم. برای مثال اگر کاربری بطور روزانه ساعتهای زیادی را در سرویسهای متعدد سایت گوگل میگذراند و یا بخشی از زندگی اجتماعی خود را در توییتر و فیس بوک سپری میکند، چیزی را تجربه میکند که ما باید تابع آن باشیم.
زمانی که شما برنامه ای را برای مشتری آماده میکنید درخواستهای حداقلی او مربوط به همین تجارب روزانه اش است. نقش Framework هایی مانند Bootstrap در اینجا نمایان میشود. در حقیقت Framework ی با عنوان Bootstrap سعی دارد تا تجارب کاربری افراد را تکرار کند و بهبود بخشد.
بنابراین اینگونه نتیجه میگیریم که یک توسعه دهنده برنامههای تحت وب بهتر است تا توانمندیهای خود را در خلق یک تجربه کاربری مناسب بهبود بخشد. در این راستا Framework هایی مانند Bootstrap کمک میکند تا ضمن حفظ استانداردها به یک طراحی منعطف رسید که امکانات حداقلی سایتها و سرویس دهندههای مطرح اینترنت را فراهم میکند.
حال اگر بحث طراحی یک وب سایت شکیل و سرشار از خلاقیت است باید از یک طراح گرافیست کمک گرفت که در حوزه وب و نرم افزار فعال باشد. برای مثال اگر حمل بر تبلیغ نشود آقای مصطفی مقدم + یکی از طراحان گرافیکی است که ضمن اشراف کامل به خلق یک اثر هنری و تأثیرگذار، به ASP.NET هم اشراف دارند. وجود چنین فردی در تیم توسعه نرم افزار و استفاده هم زمان از Framework Bootstrap به ایجاد یک ادبیات مشترک بین برنامه نویس و طراح منجر خواهد شد که در نهایت برنامه ای منعطف و مطابق با استانداردهای جهانی وب را منتج خواهد شد.
خوب است در تمام علوم مطالعه داشته باشیم اما در علومی باید تخصص کسب کرد. مطالعه و تقویت یک توانمندی خوب است اما معنای تخصص را نمیدهد. اخیراً شاهد این امر هستیم که دوستانی بدون حتی یک پروژه عملی قابل قبول در جایگاهی قرار دارند که همایشهای معتبر جهانی را در این حوزه در ایران برگزار میکنند. من مخالف این همایشها نیستم اما بهتر است به تخصصها احترام بگزاریم و صرفاً بدلیل مطالعه 2 کتاب و چند مقاله ادعای تخصص در علومی نکنیم، آن هم تا جایی که ...
امیدوارم مفید واقع شود.
نظرات مطالب
دریافت کتاب از Google books
اگر خواستید قسمتی از کتاب را از گوگل دریافت کنید و یا فصلی از کتاب و یا همه آن،راه حل ساده آن اینست که وارد temporary مرورگر شود(جایی که اطلاعات کش مرورگر ذخیره میشود و بعد از مدتی پاک می شود).و آن را در قالب تصویر دریافت کنید.
تازه می توانید کیفیت کتاب را هم مشخص کنید،به این دلیل که زوم کتاب سمت سرور می باشد،کیفت و حجم آن هم قابل تنظیم می باشد.
در ضمن در یاد داشته باشید که هر فایلی که بخواهد در مرورگر به نمایش در بیاید،قابل ذخیره کردن است D:
تازه می توانید کیفیت کتاب را هم مشخص کنید،به این دلیل که زوم کتاب سمت سرور می باشد،کیفت و حجم آن هم قابل تنظیم می باشد.
در ضمن در یاد داشته باشید که هر فایلی که بخواهد در مرورگر به نمایش در بیاید،قابل ذخیره کردن است D:
بدون شک علم برنامه نویسی در پیشرفت تکنولوژی دنیا، نقش بسیار کلیدی را ایفا کرده است بطوریکه حتی تصور یک روز بدون گوگل هم بسیار نگران کنندهاست. امروزه همهی صنعتهای دنیا، از اینترنت و سایتهایی که توسط برنامه نویسان راه اندازی میشوند، در توسعه کسب و کارهای خود استفاده میکنند. اصولا برنامه نویسی باید در استفاده از ساختههای خود برای پیشرفت و توسعهی علم خود پیشرو باشد. بدیهی ست استفادهی درست از تجربیات دیگران باعث صرفه جویی در زمان و هزینه تولید نرم افزار خواهد بود.
یک تجربه
سالها پیش یکی از همکاران تعریف میکردند که یک شرکت نرم افزاری برای مشاوره معماری نرم افزار از ایشان دعوت به همکاری کرده است. پس از مراجعه به شرکت متوجه شدند که تیم اصلی برنامه نویسان درگیر تولید ORM ای برای پروژه جدید شرکت هستند که برای تولید این ابزار بیش از 4 ماه را وقت صرف کردهاند؛ اما در مراحل نهایی کار دچار مشکلات زیادی شده اند. به نحوی که از ایشان برای کمک به رفع مشکل ORM ( به جای تولید نرم افزار مشتری) دعوت کردهاند.
در آن زمان یادم هست که EF 5 (که تقریبا نسخه سوم بعد از 3.5 و 4 میباشد - جزئیات در اینجا) توسط مایکروسافت ارائه شده بود. همچنین NHibernate هم همزمان با EFها (تاریخچه نسخهها در اینجا) قابل دسترسی بودهاست. با این حال تیم فنی به این دلیل که کوئریهای تولیدی توسط EF کند هستند، اقدام به ساخت ORM کرده بودند. جالب اینکه با بررسی بیشتر مشخص شدهاست که حجم دادههای پروژه در بدترین حالت در یک جدول به 5 هزار رکورد میرسد.
4 ماه صرف وقت و هزینه تیم 2 نفره برای طراحی و پیاده سازی و تست ORM ای که در نهایت به دلیل مشکلات Performance کنار گذاشته شد و از EF استفاده کردند. شاید در این 4 ماه میتوانستند 30 درصد پروژه اصلی را پیاده سازی کنند.
شاید بتوان 3 دلیل عمده «فنی» شکست برخی از پروژههای نرم افزاری در ایران را به شرح زیر عنوان کرد:
- عدم استفاده مناسب از ابزارها و راهکارهای موجود و انجام دوباره کاری
- استفاده غیر ضروری و عجولانه از تکنولوژیهای جدید (بدون داشتن نیروی کار مسلط)
- پایین بودن سطح فنی و بهروز نبودن برخی از برنامه نویسان ایرانی
متن باز (Open Source)
با پیشرفت توسعه نرم افزار و تمایل شرکتهای بزرگ دنیا به تولید کامپوننتهای متن باز (Open Source) ریسک استفاده از این نوع ابزارها نیز کمتر شده است. بطوریکه درصورت نیاز میتوان کامپوننت را برای پروژهها سفارش سازی کرد.
شاید کمتر کسی باور میکرد که روزی شرکت مایکروسافت محصولات خود را Open Source کند. اما امروز، در سال 2017 میلادی، شرکت مایکروسافت اقدامات مهمی را در این زمینه انجام داده است که میتوانید جزئیات پروژههای متن باز این غول کامپیوتری دنیا را در اینجا و همچنین اینجا ملاحظه کنید.
یک سناریو
فرض کنید یک پروژه تحت وب را شروع کرده اید. بدون در نظر گرفتن جزئیات پروژه میتوان گفت به ابزارهای زیر نیاز خواهید داشت:
همانطور که ملاحظه میکنید برای همهی موارد فوق ابزارهای مناسبی وجود دارند که برای پیاده سازی هر کدام، سالها وقت و هزینه صرف شدهاست. همچنین قابلیت اطمینان این ابزارها به مراتب بالاتر از ابزارهای دست ساز خواهد بود. شاید برای سادهترین ابزار فوق 3 ماه زمان لازم باشد تا یک نسخه باگ دار تهیه شود!
ملاحظات استفاده از ابزارها
توجه به چند نکته در استفاده از ابزارها و کتابخانههای آماده ضروری میباشد، بدین شرح:
- ابزار مورد نیاز را با R&D (تحقیق و توسعه) انتخاب کنید. ابزارهایی که در پروژههای واقعی استفاده شدهاند، بسیار مناسب میباشند.
- توجه داشته باشیدکه استفاده از چندین ابزار باعث ایجاد تداخل در پروژه نشود (این مورد معمولا در کامپوننتهای UI مانند JqueryUI و Bootsrtap اتفاق میافتد)
- مستندات مربوط به ابزارها را حتما مطالعه کنید. لطفا بدون تسلط از ابزاری استفاده نکنید.
درصورت امکان از ابزارها بصورت مستقیم استفاده نکنید. یک لایه واسط مخصوص خودتان را برای تنظیمات کلی ابزارها تهیه کنید که در آینده به دردتان خواهد خورد! (بیشتر در سمت سرور)
نتیجه گیری
اگر بخواهیم چرخ را اختراع نکنیم و از تجربیات موفق موجود استفاده کنیم، میتوان نتیجه گرفت که استفاده از ابزارهای آماده برای توسعه نرم افزار با رعایت دستورالعمل استفاده امری مفید میباشد. اما باید توجه داشته باشیم که استفاده از هر ابزاری به هرقیمتی در هرپروژهای، حرفه ای نیست. همهی راهکارها، ابزراها و تکنولوژیهای مورد استفاده باید در راستای هدف اصلی «تولید و تحویل به موقع نرم افزار با کیفیت به مشتری» باشد؛ هدفی که در بسیاری از موارد فراموش شده و بیشتر زمان پروژه، صرف کارهای غیر ضروری میشود.
یک تجربه
سالها پیش یکی از همکاران تعریف میکردند که یک شرکت نرم افزاری برای مشاوره معماری نرم افزار از ایشان دعوت به همکاری کرده است. پس از مراجعه به شرکت متوجه شدند که تیم اصلی برنامه نویسان درگیر تولید ORM ای برای پروژه جدید شرکت هستند که برای تولید این ابزار بیش از 4 ماه را وقت صرف کردهاند؛ اما در مراحل نهایی کار دچار مشکلات زیادی شده اند. به نحوی که از ایشان برای کمک به رفع مشکل ORM ( به جای تولید نرم افزار مشتری) دعوت کردهاند.
در آن زمان یادم هست که EF 5 (که تقریبا نسخه سوم بعد از 3.5 و 4 میباشد - جزئیات در اینجا) توسط مایکروسافت ارائه شده بود. همچنین NHibernate هم همزمان با EFها (تاریخچه نسخهها در اینجا) قابل دسترسی بودهاست. با این حال تیم فنی به این دلیل که کوئریهای تولیدی توسط EF کند هستند، اقدام به ساخت ORM کرده بودند. جالب اینکه با بررسی بیشتر مشخص شدهاست که حجم دادههای پروژه در بدترین حالت در یک جدول به 5 هزار رکورد میرسد.
4 ماه صرف وقت و هزینه تیم 2 نفره برای طراحی و پیاده سازی و تست ORM ای که در نهایت به دلیل مشکلات Performance کنار گذاشته شد و از EF استفاده کردند. شاید در این 4 ماه میتوانستند 30 درصد پروژه اصلی را پیاده سازی کنند.
شاید بتوان 3 دلیل عمده «فنی» شکست برخی از پروژههای نرم افزاری در ایران را به شرح زیر عنوان کرد:
- عدم استفاده مناسب از ابزارها و راهکارهای موجود و انجام دوباره کاری
- استفاده غیر ضروری و عجولانه از تکنولوژیهای جدید (بدون داشتن نیروی کار مسلط)
- پایین بودن سطح فنی و بهروز نبودن برخی از برنامه نویسان ایرانی
متن باز (Open Source)
با پیشرفت توسعه نرم افزار و تمایل شرکتهای بزرگ دنیا به تولید کامپوننتهای متن باز (Open Source) ریسک استفاده از این نوع ابزارها نیز کمتر شده است. بطوریکه درصورت نیاز میتوان کامپوننت را برای پروژهها سفارش سازی کرد.
شاید کمتر کسی باور میکرد که روزی شرکت مایکروسافت محصولات خود را Open Source کند. اما امروز، در سال 2017 میلادی، شرکت مایکروسافت اقدامات مهمی را در این زمینه انجام داده است که میتوانید جزئیات پروژههای متن باز این غول کامپیوتری دنیا را در اینجا و همچنین اینجا ملاحظه کنید.
یک سناریو
فرض کنید یک پروژه تحت وب را شروع کرده اید. بدون در نظر گرفتن جزئیات پروژه میتوان گفت به ابزارهای زیر نیاز خواهید داشت:
ابزار | مثال |
ORM | EF , NHibernate , Dapper , LLBLGEN |
IOC COntainer | Unity , StructureMap , Autofac , Castle.Windsor, LightInject , Ninject |
Report Tools | CrsytalReport , Stimusoft , DevExpress Report, Telerik Report Tools, EasyReport |
UI Component | Telerik , JqueryUI , Bootsrap ,CompnentArt, ComponentOne |
Error Logger | ELMAH , NLog , log4net |
Mapper Tools | AutoMapper , ValueInjecter |
ملاحظات استفاده از ابزارها
توجه به چند نکته در استفاده از ابزارها و کتابخانههای آماده ضروری میباشد، بدین شرح:
- ابزار مورد نیاز را با R&D (تحقیق و توسعه) انتخاب کنید. ابزارهایی که در پروژههای واقعی استفاده شدهاند، بسیار مناسب میباشند.
- توجه داشته باشیدکه استفاده از چندین ابزار باعث ایجاد تداخل در پروژه نشود (این مورد معمولا در کامپوننتهای UI مانند JqueryUI و Bootsrtap اتفاق میافتد)
- مستندات مربوط به ابزارها را حتما مطالعه کنید. لطفا بدون تسلط از ابزاری استفاده نکنید.
گاهی پیش میآید که یک برنامه نویس بدون مطالعه مستندات مربوط به یک IOC Container از آن ابزار استفاده میکند و در Register اولیه ویژگی LifeCycle مربوط به Context را با حالت Singleton مقداردهی میکند. بدین ترتیب پس از نیم ساعت، پروژه به دلیل آنچه که میتوان "چاقی Context" نامید، DONE یا حداقل کند میشود که رفع این مشکل ساعتها زمان میبرد.
درصورت امکان از ابزارها بصورت مستقیم استفاده نکنید. یک لایه واسط مخصوص خودتان را برای تنظیمات کلی ابزارها تهیه کنید که در آینده به دردتان خواهد خورد! (بیشتر در سمت سرور)
فرض کنید در پروژه WPF از کامپوننتهای زیبای DevExpress استفاده میکنید. به ازای هر کامپوننت یک کلاس به پروژه اضافه کنید که از کلاس اصلی آن کامپوننت Devexspress ارث میبرد و در لایه UI خود از کلاس جدید خود استفاده کنید. با این کار میتوانید ویژگیهای عمومی کامپوننتها را یکبار برای کل پروژه اعمال کنید.
نتیجه گیری
اگر بخواهیم چرخ را اختراع نکنیم و از تجربیات موفق موجود استفاده کنیم، میتوان نتیجه گرفت که استفاده از ابزارهای آماده برای توسعه نرم افزار با رعایت دستورالعمل استفاده امری مفید میباشد. اما باید توجه داشته باشیم که استفاده از هر ابزاری به هرقیمتی در هرپروژهای، حرفه ای نیست. همهی راهکارها، ابزراها و تکنولوژیهای مورد استفاده باید در راستای هدف اصلی «تولید و تحویل به موقع نرم افزار با کیفیت به مشتری» باشد؛ هدفی که در بسیاری از موارد فراموش شده و بیشتر زمان پروژه، صرف کارهای غیر ضروری میشود.
در برنامههای مبتنی بر وب رایج، معمولا تبدیل تاریخ میلادی به شمسی در سمت سرور انجام میگیرد و تاریخ شمسی حاصل از تبدیل، به کاربر نمایش داده میشود. اما در برنامههای Single Page و یا به اختصار SPAها که کلاینت فقط با یک سری داده به فرمت JSON درگیر است، برای نمایش تاریخ شمسی به چه طریقی باید عمل کرد؟ آیا باید تاریخ را در سمت سرور به فرمت مورد نظر تبدیل کرد و یا در سمت کلاینت؟ همهی اینها از جمله سوالاتی هست که به هنگام توسعهی SPAها با آنها حتما درگیر خواهید شد.
شاید بتوان گفت که در SPA ها، هدف این است که از بار سرور تا حد ممکن کم کرد و آن را در بین کلاینتها توزیع کرد. در SPAها نقش اصلی سرور تامین داده هاست و بیشتر پردازشها در صورت امکان در سمت کلاینت انجام میشود و میبینید که حتی رندر کردن HTML نیز به عهدهی قالبهای سمت کلاینت است. البته هنوز هم میتوان قبل از اینکه داده را به فرمت JSON سریالایز کرد، سمت سرور بر روی آنها پیمایش انجام داده و تاریخهای میلادی را به شمسی تبدیل کرد که هدف ما این نیست و میخواهیم این کار را بر عهدهی مرورگر کاربر قرار دهیم.
معرفی moment.js
برای کار با دادههایی از جنس تاریخ در سمت کلاینت، کتابخانهی جاوا اسکریپتی قدرتمندی به نام moment.js وجود دارد. این کتابخانه دارای انواع و اقسام API برای نمایش و پردازش تاریخ هست. حتی میتواند relative time را نیز نمایش دهد. منظور از relative time این هست که به جای نمایش تاریخ، اختلاف آن را با زمان حال نمایش دهد. برای مثال مینویسند فلان پست در دو ساعت پیش ارسال شده و زمان دقیق ارسال پست را نمایش نمیدهد.
خوشبختانه برای افزودن تاریخ شمسی به این کتاب خانه، افزونهای به نام moment-jalaali برای آن تدارک دیده شده است. کار با آن نیز بسیار راحت است. کافی است در همان API هایی که برای فرمت کردن تاریخ در moment.js استفاده میکردید؛ یک j در ابتدای آنها قرار دهید که مثالهای کامل استفاده از آن را در مستندات آن میتوانید مشاهده کنید.
نحوهی استفاده از moment.js در AngularJS و ASP.NET
در ASP.NET فیلد هایی که از جنس DateTime هستند به شکل زیر به فرمت JSON سریالایز میشوند:
\/Date(1374222094520)\/
moment("/Date(1198908717056-0700)/"); // December 28 2007 10:11 PM
{{ name | uppercase }}
{{post.date | jalaliDate:'jYYYY/jMM/jDD hh:mm' }}
تعریف فیلتر jalaliDate نیز به شکل زیر است:
app.filter('jalaliDate', function () { return function (inputDate, format) { var date = moment(inputDate); return date.fromNow() + " " + date.format(format); } });
خروجی این فیلتر نیز به شکل "4 ماه پیش 1392/12/07 03:10" است و مشاهده میکنید که به کمک filterها در AngularJS انجام این گونه از کارها بسیار ساده و لذت بخش است.
توجه کنید که این فقط یک ایدهی ابتدایی و ساده از پیاده سازی فیلتر فوق است. قطعا با کمک APIهای متنوع momentjs و پارامترهای ورودی فیلتر، میتوان فیلتری بسیار پیشرفتهتر تعریف کرد.
دریافت کدهای یک مثال پیاده سازی شده با استفاده از کدهای فوق
سلام
روش شما خوبه اما عیبی که داره اینه که هر ثانیه ساعت از سرور میگیره و باعث سربار میشه
به نظر من روش بهتر اینه از همون کد طرف کلاینت استفاده بشه اما زمان اولیه از سرور خونده بشه بعد طرف کلاینت این ساعت خودمون اضافه کنیم و ساعت شبیه سازی کنیم
در صورتی هم که کاربر به صفحه دیگری بره یا صفحه رفرش کنه دوباره ساعت از سرور خونده میشه و الگوریتم ساعت طرف کلاینت به کار میافته
به نظر من این روش بهینهتر هست حالا باز ببینیم دوستان چه نظری دارن
موفق وموید باشید
روش شما خوبه اما عیبی که داره اینه که هر ثانیه ساعت از سرور میگیره و باعث سربار میشه
به نظر من روش بهتر اینه از همون کد طرف کلاینت استفاده بشه اما زمان اولیه از سرور خونده بشه بعد طرف کلاینت این ساعت خودمون اضافه کنیم و ساعت شبیه سازی کنیم
در صورتی هم که کاربر به صفحه دیگری بره یا صفحه رفرش کنه دوباره ساعت از سرور خونده میشه و الگوریتم ساعت طرف کلاینت به کار میافته
به نظر من این روش بهینهتر هست حالا باز ببینیم دوستان چه نظری دارن
موفق وموید باشید