مطالب
آموزش زمانبندی کارها با HangFire در Asp.Net Core

تسک‌های پس زمینه (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
البته میتوانید آدرس داشبورد 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.Hourly) :
این Cron هر یک ساعت یکبار و بصورت پیش‌فرض در دقیقه اول هر ساعت اجرا میشود. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly);
اما میتوانید یک ورودی دقیقه به آن بدهید که در اینصورت در N اُمین دقیقه از هر ساعت اجرا شود. 
 _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));
با اجرای این متد، اول کاری برای تکرار در زمانبندی ماهیانه ایجاد میشود و در متد دوم، زمانی برای حذف متد اول مشخص میکند.

در آخر امیدوارم این مقاله برایتان مفید واقع شده باشد. میتوانید فیدبکتان را در قالب کامنت یا یک قهوه برایم ارسال کنید.

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
یک نکته‌ی تکمیلی: امکان اجباری کردن وجود قسمتی در تنظیمات برنامه در دات نت 6

در مطلب فوق، نمونه‌ی استفاده از متد Configuration.GetSection را مشاهده می‌کنید؛ مانند: Configuration.GetSection("Auth:Users"). در دات نت 6 متد جدیدی به نام GetRequiredSection نیز بجای GetSection معرفی شده‌است که در صورت وجود نداشتن چنین قسمتی در فایل تنظیمات برنامه، یک استثناء را در ابتدای کار و شروع آن، صادر می‌کند.
مطالب دوره‌ها
اعمال غیر همزمان و چند ریسمانی
تصور عموم بر آن است که اعمال غیر همزمان با چند ریسمانی به یک معنا هستند. این مورد الزاما صحیح نیست. برای مثال دریافت غیرهمزمان یک فایل را از اینترنت درنظر بگیرید. شاید اینطور به نظر برسد که در اینجا یک ترد جدید ایجاد شده و در آن کل کار دریافت فایل آغاز می‌گردد؛ اما خیر. ایجاد یک ترد جدید تنها در قسمت‌های خاصی از یک پروسه انجام می‌شود. همچنین از لحاظ فنی امکان انجام کل کار در یک ترد، بدون بلاک کردن آن وجود دارد. از این جهت که بیشتر زمان، جهت صبر کردن دریافت پاسخی از سرور صرف می‌شود. زمانیکه کلاینت درخواستی را ارسال می‌کند، دیگر کار خاصی را نمی‌تواند انجام دهد تا اینکه پاسخی را دریافت کند.
زمانیکه از یک API غیرهمزمان برای مدیریت چنین عملیاتی استفاده می‌شود، ترد جاری را در این حالت در خواب فرو می‌برد. برای اینکه کار بیشتری برای انجام وجود ندارد. همچنین با اینکه کلاینت درخواستی را ارسال می‌کند یا پاسخی را دریافت، برای مدیریت کل عملیات در اکثر اوقات نیازی به تردها ندارد. این سخت افزار شبکه‌ی نصب شده در سیستم است که عمده‌ی کار را انجام می‌دهد و نه برنامه. زمانیکه برنامه درخواست ارسال اطلاعاتی را بر روی شبکه ارائه می‌دهد، درایور سخت افزار شبکه است که به سخت افزار مرتبط فرمان می‌دهد چه اطلاعاتی را باید ارسال کند. اکثر اینگونه سخت افزارها قادرند اطلاعات را خارج از حافظه‌ی اصلی سیستم دریافت کنند. در اینجا درایور تنها باید به سخت افزار عنوان کند، چه اطلاعاتی را و به کجا باید ارسال کند. بنابراین CPU تنها در طی ارسال این فرمان است که مشغول می‌باشد و نه خارج از آن و این زمان اصلا در مقایسه با زمان ارسال اطلاعات توسط سخت افزار مرتبط، طولانی نیست. CPU  مجددا زمانی درگیر خواهد شد که سخت افزار شبکه، اطلاعاتی را دریافت کرده است و باز هم این زمان در مقایسه با زمان دریافت اطلاعات توسط سخت افزار شبکه بسیار کوتاه است.
اغلب کارهای IO به همین شکل هستند. شبیه به همین روند در حالت دسترسی به سخت دیسک وجود دارد. مدت زمانیکه CPU به دیسک کنترلر اعلام می‌کند چه اطلاعاتی را نیاز دارد در مقایسه با مدت زمانیکه دیسک کنترلر این اطلاعات را واقعا بارگذاری می‌کند، بسیار ناچیز است.
نمونه‌ی دیگر آن کار با بانک‌های اطلاعاتی است. در اغلب اوقات برنامه‌ی ما صرفا یک درخواست را به بانک اطلاعاتی ارائه می‌دهد و اصل عملیات در جایی دیگر و توسط موتور بانک اطلاعاتی، خارج از برنامه پردازش می‌گردد.
بنابراین جهت پردازش یک پروسه‌ی خاص، در بسیاری از مراحل آن تنها یک ترد کافی است و هدف اصلی اعمال غیرهمزمان، کاهش تعداد تردهایی است که برنامه جهت پردازش عملیاتی خاص، نیاز دارد. این نوع الگوریتم‌ها طوری طراحی شده‌اند تا تردها تنها زمانی بکار گرفته شود که واقعا CPU قرار است کار خاصی را انجام دهد و نه برای مثال زمانیکه دیسک کنترلر یا سخت افزار شبکه مشغول به کار هستند (و ویندوز به صورت توکار دارای یک چنین API ایی هست). این مساله در سمت کلاینت، سبب خواهد شد تا ترد UI آزاد شود و بتواند به درخواست‌های رسیده کاربر بهتر پاسخ دهد. همچنین این مساله در سمت سرور نیز بسیار مفید است، زیرا برنامه قادر خواهد شد تا به تعداد بیشتری از درخواست‌ها به صورت همزمان پاسخ دهد. زیرا با کاهش تعداد تردهای درگیر، مقیاس پذیری سیستم افزایش می‌یابد.
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت دوم
در قسمت اول در مورد ابزار انتخابی برای توسعه برنامه‌های Cross Platform صحبت کردیم. در این قسمت به آموزش نصب و راه اندازی محیط توسعه می‌پردازیم.

شما می‌توانید در هر یک از سیستم عامل‌های Mac - Windows - Linux و با هر یک از IDE‌های Visual Studio - Visual Studio for mac - Rider کار کنید. برای این که بتوانیم آموزش را کاملا عملی پیش ببریم و وارد جزئیات شویم، در عمل باید یکی را انتخاب و آموزش دهیم و آن Windows - Visual Studio است؛ اگرچه باقی تفاوت خیلی زیادی ندارند.

با توجه به این که کد نوشته شده برای UI و Logic برای هر سه پلتفرم Windows - Android - iOS یکی است و کدهای منحصر به هر پلتفرم، سهم اندکی از پروژه را تشکیل می‌دهد و همچنین تست برنامه برای Windows آسان‌تر و سریع‌تر بوده و امکانات بیشتری را دارد، توصیه اکید می‌کنم برنامه را روی نسخه Windows توسعه دهید و تست کنید و پس از انجام کارهای اصلی پروژه، آن را بر روی Android و iOS نیز تست کنید. این مورد شباهت به برنامه نویسی وب برای مرورگرها را دارد. خیلی از افراد، سایت را بر روی یک مرورگر مثل Chrome یا Firefox توسعه می‌دهند و در نهایت کار را بر روی مرورگرهای موبایل و IE - Edge - Safari و ... تست می‌کنند. همانطور که می‌شود در مرورگر Chrome هم Touch را تست کرد و هم سایزهای مختلف را، همین کارها را در تست نسخه ویندوزی نیز می‌توانید انجام دهید. در کنار این با توجه به رشد فروش تبلت‌های ویندوزی، برای خیلی از برنامه‌ها، ارائه نسخه ویندوزی می‌تواند مفید نیز باشد.

برای شروع بهتر است نسخه‌ای به روز از ویندوز 10 را داشته باشید، یا Pro یا Enterprise. برای بررسی، ابتدا Command Line را باز کنید و دستور ver (مخفف version) را اجرا کنید. چیزی مشابه مقدار
Microsoft Windows Version 10.0.17134.345
را مشاهده خواهید نمود که باید عدد پنج رقمی آن (در این مثال 17134) از 16299 کمتر نباشد. اگر فرض کنیم که فقط یک سیستم داریم که بدون سیستم عامل است، تا این جا یک ISO نصب ویندوز دانلود کرده‌ایم، به حجم 3.7 گیگ که بعد از نصب، 9.5 گیگ از فضای هارد را می‌گیرد. کمی حوصله به خرج دهید (!) و اگر می‌خواهید همه چیز را تمیز انجام دهید، با یک ویندوز تمیز شروع کنید!

آخرین نسخه پایدار ویژوال استودیو در زمان نگارش این مقاله، 2017 - 15.8.7 هست که ما نیاز به نصب Workload های زیر داریم:
Universal Windows Platform development 
Mobile development with .NET 
نصب این دو احتیاج به 5 گیگ دانلود و 14 گیگ فضای روی هارد را دارد که علاوه بر خود Visual Studio و محیط توسعه آن، موارد زیر را نیز برای شما نصب می‌کند:
 Android SDK - Android NDK - JDK (Java) - Windows SDK - iOS SDK
نکات مهم:
۱- اگر قبلا یکی از SDK‌های ذکر شده را دانلود کرده‌اید، لطفا بی خیال آن شوید! اجازه دهید تا ویژوال استودیو همه چیز را دانلود و نصب و کانفیگ کند. Android SDK، برای مثال، بالغ بر 70 گیگ فایل، برای 28 ورژن اندروید است که اگر یکی از آن‌ها را داشته‌اید که برای تست کد نویسی با Java و Android Studio جواب می‌داده، هیچ دلیلی ندارد دقیقا همان نسخه به درد Xamarin هم بخورد!
۲- ترجیحا نسخه Enterprise را نصب کنید.
۳- قسمت‌های عمده فایل‌های دانلودی از سرورهای مایکروسافت دانلود می‌شوند که محدودیتی برای کاربران ایرانی ندارد، ولی قسمت هایی نیز مستقیما از سرورهای گوگل دانلود می‌شوند که متاسفانه روی کاربرهای ایرانی بسته است. با توجه به این که ممکن است استفاده از روش‌های دور زدن تحریم مانند VPN باعث کندی سرعت اینترنت و دانلود شوند، توصیه می‌کنم که ابتدا "Universal Windows Platform development" را نصب کنید (زیرا تماما از سرورهای مایکروسافت دانلود می‌شود) و سپس مجدد Installer را باز کرده و "Mobile development with .NET" را انتخاب کنید و این بار از ابزارهای دور زدن تحریم استفاده کنید.
۴- در سمت راست گزینه‌های قابل نصب، تیک موارد "Google Android Emulator API Level 27" و "Intel Hardware Accelerated Execution Manager (HAXM) global install" را بردارید. در پستی جداگانه آپشن‌های متنوع Emulator‌های اندرویدی را بررسی خواهیم نمود.
۵- بهتر است Administrator سیستم خود باشید.

بعد از اتمام نصب باید Developer mode را فعال کنید که نحوه انجام آن در این لینک شرح داده شده است. به صورت خلاصه به Settings بروید، سپس Update & Security، سپس For developers و در نهایت انتخاب Developer mode از بین گزینه‌های موجود.
ضمن استفاده از ابزارهای دور زدن تحریم (فقط برای ساختن و بیلد کردن اولین پروژه)، ویژوال استودیو را به صورت Run as admin باز کنید و از منوی File > New > Project قسمت Cross-Platform برای CSharp، یک Mobile app Xamarin Forms بسازید که ضمن انگلیسی بودن نام پروژه و فاقد Space بودن آن، ترجیحا در فولدری باشد که مسیر آن فولدر نیز طولانی نباشد، Space و کارکترهای فارسی نیز نداشته باشد.

تنظیماتی که در پنجره New Cross Platform App هستند مناسب بوده و Ok را بزنید! اولین بیلد به علت نیاز به دانلود طول می‌کشد و در صورت بیلد شدن موفقیت آمیز پروژه شما دومین قسمت را با موفقیت طی کرده اید. در قسمت بعدی ساختار پروژه‌های Xamarin Forms را بررسی می‌کنیم و یک مثال ساده می‌نویسیم که لااقل روی ویندوز قابلیت تست را داشته باشد. دقت کنید که همان کد روی Android / iOS نیز کار می‌کند، ولی در پست هایی جداگانه باید در مورد راه اندازی Emulator‌های Android و iOS آموزش هایی را ببینید. در صورت وجود هر گونه مشکل یا سوال نیز در قسمت نظرات همین صفحه در خدمت شما عزیزان هستیم. 
مطالب
مسیریابی در Angular - قسمت اول - معرفی
مسیریابی در +Angular 2 به همراه قابلیت‌های فراوان و ویژه‌ای است که تعدادی از آن‌ها را تابحال در این سایت بررسی کرده‌ایم. مورد مقدماتی اول، نیاز به بازنویسی کامل دارد، مورد دوم جهت آشنایی با ساختار Angular CLI مفید است و مورد سوم یکی از مباحث تکمیلی آن است. به همین جهت در طی یک سری، ویژگی‌های متعدد سیستم مسیریابی +Angular 2 را از ابتدا بررسی خواهیم کرد.


مسیریابی در +Angular 2

عموما از مسیریابی جهت حرکت بین Viewهای مختلف برنامه استفاده می‌شود، اما کارهای بیشتری را نیز می‌توان با آن انجام داد؛ مانند ارسال اطلاعات، به مسیریابی‌ها، پیش بارگذاری اطلاعات، جهت نمایش در Viewها، گروه بندی و محافظت از مسیریابی‌ها، پویانمایی و انیمیشن و همچنین بهبود کارآیی، با بارگذاری async مسیرهای مختلف.
کار سیستم مسیریاب +Angular 2 زمانی شروع می‌شود که تغییری را در آدرس درخواستی از برنامه مشاهده می‌کند؛ یا از طریق درخواست آدرسی توسط مرورگر و یا هدایت به قسمتی خاص، از طریق کدنویسی. سپس مسیریاب به آرایه‌ی تنظیم شده‌ی مسیرهای سیستم مراجعه می‌کند تا بتواند تطابقی را بین آدرس درخواستی و یکی از کلیدهای تنظیم شده‌ی در آن پیدا کند. در این حالت اگر تطابقی یافت نشود، کارمسیریابی خاتمه خواهد یافت. در غیراینصورت کار ادامه یافته و سپس مسیریاب، محافظ‌های مسیر درخواستی را بررسی می‌کند تا مشخص شود که آیا کاربر مجاز به هدایت به این قسمت خاص از برنامه هست یا خیر؟ در صورت مثبت بودن پاسخ، مرحله‌ی بعد، پیش بارگذاری اطلاعات درخواستی جهت نمایش View مرتبط است. در ادامه کامپوننت متناظر با مسیریابی فعالسازی می‌شود. سپس قالب این کامپوننت را در قسمتی که توسط router-outlet مشخص می‌گردد، جایگذاری کرده و نمایش می‌دهد.


تعریف مسیر پایه یا Base path

اولین مرحله‌ی کار با سیستم مسیریابی +Angular 2، تعریف یک base path است. مسیرپایه، به زیرپوشه‌ای اشاره می‌کند که برنامه‌ی ما در آن قرار گرفته‌است:
 www.mysite.com/myapp
در این مثال، مسیرپایه برنامه، /myapp/ است.
مسیریاب از این مسیرپایه جهت ساخت آدرس‌های مسیریابی استفاده می‌کند. مقدار آن نیز به صورت ذیل در فایل index.html برنامه، درست پس از تگ head تعیین می‌گردد:
<!DOCTYPE html>
<html>
<head>
    <base href="/">
در اینجا با ذکر / ، به عنوان مسیرپایه، به ریشه‌ی سایت اشاره شده‌است. اگر برنامه‌ی شما قرار است در یک زیرپوشه قرار بگیرد، در این حالت نحوه‌ی تعیین این زیر پوشه به صورت ذیل خواهد بود:
<base href="/myapp/">


تعیین مسیرپایه جهت ارائه‌ی نهایی

استفاده از مسیر پایه / برای حالت توسعه و همچنین زمانیکه برنامه‌ی نهایی شما در ریشه‌ی سایت توزیع می‌شود، بسیار مناسب است. اما اگر برای حالت توسعه از مقدار / و برای حالت توزیع از مقدار /myapp/ بخواهید استفاده کنید، مدام نیاز خواهید داشت تا فایل index.html نهایی سایت را ویرایش کنید. برای این منظور Angular CLI دارای پرچمی است به نام base-href:
 > ng build --base-href /myapp/
به این ترتیب در زمان ساخت و توزیع نهایی برنامه توسط Angular CLI، کار تنظیم خودکار مسیرپایه نیز صورت خواهد گرفت.
حالت پیش فرض تولید برنامه‌های Angular توسط Angular CLI، تنظیم مسیرپایه در فایل src\index.html به صورت خودکار به / می‌باشد.


تعریف مسیریاب Angular

مسیریاب Angular در ماژولی به نام RouterModule قرار گرفته‌است و باید در ابتدای کار import شود. این ماژول شامل سرویسی است جهت هدایت کاربران به صفحات دیگر و مدیریت URLها، تنظیماتی برای تعریف جزئیات مسیریابی‌ها و تعدادی دایرکتیو که برای فعالسازی و نمایش مسیرها از آن‌ها استفاده می‌شود. برای مثال دایرکتیو RouterLink آن یک المان قابل کلیک HTML را به مسیر و کامپوننتی خاص در برنامه متصل می‌کند. RouterLinkActive، شیوه‌نامه‌ها را به لینک فعال انتساب می‌دهد و RouterOutlet محل نمایش قالب کامپوننت فعال شده را مشخص می‌کند.

یک مثال: در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام angular-routing-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 > ng new angular-routing-lab --routing
به علاوه قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید تا بوت استرپ نیز نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
 > npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه اگر به فایل src\app\app-routing.module.ts مراجعه کنید، یک چنین محتوایی را خواهید یافت:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    children: []
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا برای شروع به کار با سیستم مسیریابی، RouterModule معرفی شده‌است. ابتدا import آن‌را مشاهده می‌کنید و سپس این ماژول به قسمت imports مربوط به NgModule اضافه شده‌است.
می‌توان این قسمت را خلاصه کرد و فایل app-routing.module.ts را نیز حذف کرد و سپس import لازم و تعریف ماژول آن‌را به ماژول آغازین برنامه یا همان src\app\app.module.ts نیز منتقل کرد. اما پس از مدتی تنظیمات مسیریابی آن، فایل ماژول اصلی برنامه را بیش از اندازه شلوغ خواهند کرد. بنابراین Angular-CLI تصمیم به ایجاد یک ماژول مستقل را برای تعریف تنظیمات مسیریابی برنامه گرفته‌است. سپس تعریف آن را به فایل src\app\app.module.ts به صورت خودکار اضافه می‌کند:
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  imports: [
    AppRoutingModule
  ],

اگر به قسمت import مربوط به NgModule فایل src\app\app-routing.module.ts دقت کنید، این ماژول به همراه متد forRoot معرفی شده‌است.
@NgModule({
  imports: [RouterModule.forRoot(routes)],
ماژول مسیریابی به همراه دو متد ذیل است:
الف) forRoot
- کار آن تعریف دایرکتیوهای مسیریابی، مدیریت تنظیمات مسیریابی و ثبت سرویس مسیریابی است.
- نکته‌ی مهم اینجا است که متد forRoot تنها یکبار باید در طول عمر یک برنامه تعریف شود.
- این متد آرایه‌ای از تنظیمات مسیریابی‌های تعریف شده را دریافت می‌کند.
ب) forChild
- کار آن تعریف دایرکتیوهای مسیریابی و مدیریت تنظیمات مسیریابی است؛ اما سرویس مسیریابی را مجددا ثبت نمی‌کند.
- از این متد در جهت تعریف مسیریابی‌های ماژول‌های ویژگی‌های مختلف برنامه و نظم بخشیدن به آن‌ها استفاده می‌شود.

بنابراین زمانیکه از forRoot استفاده می‌شود، سرویس مسیریابی تنها یکبار ثبت خواهد شد و تنها یک وهله از آن موجود خواهد بود. در ادامه هر کدام از ماژول‌های دیگر برنامه می‌توانند forChild خاص خودشان را داشته باشند.

اکنون تمام کامپوننت‌های قید شده‌ی در قسمت declaration، امکان دسترسی به دایرکتیوهای مسیریابی را پیدا می‌کنند. همچنین از آنجائیکه AppRoutingModule به همراه متد forRoot است، سرویس مسیریابی نیز در کل برنامه قابل دسترسی است.


تنظیمات اولیه مسیریابی برنامه

آرایه‌ی const routes: Routes فایل src\app\app-routing.module.ts در ابتدای کار خالی است. در اینجا کار تعریف URL segments و سپس اتصال آن‌ها به کامپوننت‌های متناظری جهت فعالسازی و نمایش قالب آن‌ها صورت می‌گیرد. این نمایش نیز در محل router-outlet تعریف شده‌ی در فایل src\app\app.component.html انجام می‌شود:
<h1>
  {{title}}
</h1>
<router-outlet></router-outlet>
به ازای هر router-outlet، تنها یک مسیر را در هر زمان می‌توان نمایش داد. بنابراین با فعال شدن هر کامپوننت، قالب آن، قالب کامپوننت پیشین را در router-outlet جایگزین خواهد کرد. امکان تعریف router-outletهای دیگری نیز وجود دارند که به آن‌ها secondary routes گفته می‌شود.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمد گویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد، به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],

سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا در ابتدا دو کامپوننت جدید تعریف شده، import شده‌اند.

یک نکته: افزونه‌ی auto import، کار تعریف کامپوننت‌ها را در VSCode بسیار ساده می‌کند و امکان تشکیل خودکار قسمت import را با ارائه‌ی یک intellisense به همراه دارد.

سپس کار تکمیل آرایه‌ی Routes انجام شده‌است. همانطور که مشاهده می‌کنید، این آرایه متشکل است از اشیایی که به همراه خاصیت path و سایر پارامترهای مورد نیاز هستند.
کار خاصیت path، تعیین URL segment متناظری است که این مسیریابی را فعال می‌کند. برای مثال اولین شیء تعریف شده با آدرس‌هایی مانند www.mysite.com/welcome متناظر است.
 { path: 'welcome', component: WelcomeComponent },
در اغلب حالات به همراه path، خاصیت کامپوننت متناظر با آن مسیر نیز ذکر می‌شود؛ مانندWelcomeComponent ایی که در اینجا مقدار دهی شده‌است. هنگامیکه این مسیریابی فعال می‌شود، قالب این کامپوننت‌است که در router-outlet نمایش داده خواهد شد (فایل src\app\welcome\welcome.component.html).

چند نکته:
- در حین تعریف مقدار خاصیت path، هیچ / آغاز کننده‌ای تعریف نشده‌است.
- مقدار خاصیت path، حساس به کوچکی و بزرگی حروف است.
- WelcomeComponent تعریف شده، یک رشته نیست و ارجاعی را به کامپوننت مرتبط دارد. به همین جهت نیاز به import statement ابتدایی را دارد و وجود آن توسط کامپایلر بررسی می‌شود.


تعیین مسیریابی پیش فرض سایت

اما زمانیکه برنامه برای بار اول بارگذاری می‌شود، چطور؟ در این حالت هیچ URL segment ایی وجود ندارد. بنابراین برای تنظیم مسیرپیش فرض سایت، خاصیت path، به یک رشته‌ی خالی همانند دومین شیء تنظیمات مسیریابی، تنظیم می‌شود:
   { path: '', redirectTo: 'welcome', pathMatch: 'full' },
در اینجا زمانیکه کاربر، ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد. در این تنظیم خاصیت pathMatch نحوه‌ی تطابق با خاصیت path را مشخص می‌کند. مقدار full نیز به این معنا است که اگر تمام URL segment دریافتی خالی بود، از این تنظیم استفاده کن.


مدیریت مسیریابی آدرس‌های ناموجود در سایت

تنظیم سومی را نیز در اینجا مشاهده می‌کنید:
 { path: '**', component: PageNotFoundComponent },
** به این معنا است که اگر مسیریاب موفق به تطابق URL segment دریافتی، با هیچکدام از pathهای تعریف شده نبود، از این تنظیم استفاده کند (catch all). برای مثال اگر کاربر آدرس www.mysite.com/someAddr را درخواست کند، چون URL segment آن در آرایه‌ی تنظیمات، تعریف نشده‌است، می‌خواهیم یک صفحه‌ی یافت نشد را نمایش دهیم.

یک نکته: ترتیب مسیریابی‌ها در آرایه‌ی تعریف آن‌ها اهمیت دارد. در اینجا از استراتژی «اولین تطابق یافته‌، برنده خواهد بود» استفاده می‌شود. بنابراین تنظیم ** باید در انتهای لیست ذکر شود؛ در غیراینصورت هیچکدام از مسیریابی‌های تعریف شده‌ی پس از آن پردازش نخواهند شد.


مدیریت تغییرات آدرس‌های برنامه

در طول عمر برنامه ممکن است نیاز به تغییر آدرس‌های برنامه باشد. برای مثال بجای مسیر welcome مسیر home نمایش داده شود:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
در این تنظیمات جدید برنامه، مسیر home به WelcomeComponent اشاره می‌کند. اما جهت هدایت آدرس‌های قدیمی welcome، به این آدرس جدید home می‌توان از redirectTo، مانند دومین شیء مسیریابی اضافه شده‌ی به آرایه‌ی تنظیمات استفاده کرد. به این ترتیب هم تغییرات آرایه‌ی Routes به حداقل می‌رسد و هم آدرس‌های ذخیره شده‌ی توسط کاربران هنوز هم معتبر خواهند بود.

نکته: redirectToها قابلیت تعریف زنجیره‌ای را ندارند. به این معنا که اگر ریشه‌ی سایت درخواست شود، ابتدا به مسیر welcome هدایت خواهیم شد. مسیر welcome هم یک redirectTo دیگر به مسیر home را دارد. اما در اینجا کار به این redirectTo دوم نخواهد رسید و این پردازش، زنجیره‌ای نیست. بنابراین مسیریابی پیش‌فرض را نیز باید ویرایش کرد و به home تغییر داد:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
به این ترتیب دیگر درخواست زنجیره‌وار redirectToها رخ نخواهد داد.

نکته: redirectToها می‌توانند local و یا absolute باشند. تعریف محلی آن‌ها مانند ذکر home و welcome در اینجا است و تنها سبب تغییر یک URL segment می‌شود. اما اگر در ابتدای مقادیر redirectToها یک / قرار دهیم، به معنای تعریف یک مسیر مطلق است و کل URL را جایگزین می‌کند.


تعیین محل نمایش قالب‌های کامپوننت‌ها

زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <a class="navbar-brand">{{title}}</a>
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/home']">Home</a>
      </li>
    </ul>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>
در اینجا دایرکتیو router-outlet محلی است که قالب کامپوننت فعالسازی شده، نمایش داده می‌شود. برای مثال اگر کاربر بر روی لینک Home کلیک کند، کامپوننت متناظر با آن از طریق تنظیمات مسیریابی یافت شده و فعالسازی می‌شود و سپس قالب آن در محل router-outlet رندر خواهد شد.

یک نکته: چون کامپوننت welcome از طریق مسیریابی نمایش داده می‌شود و دیگر به صورت مستقیم با درج تگ selector آن در صفحه فعالسازی نخواهد شد، می‌توان به تعریف کامپوننت آن مراجعه کرده و selector آن‌را حذف کرد.
 @Component({
//selector: 'app-welcome',
templateUrl: './welcome.component.html',
styleUrls: ['./welcome.component.css']
})

تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد:


اگر به آدرس تنظیم شده‌ی در مرورگر دقت کنید، http://localhost:4200/home آدرسی است که در ابتدای نمایش سایت نمایان خواهد شد. علت آن نیز به تنظیم مسیریابی پیش فرض سایت برمی‌گردد.
و اگر یک مسیر غیرموجود را درخواست دهیم، قالب کامپوننت PageNotFound ظاهر می‌شود:



هدایت کاربران به قسمت‌های مختلف برنامه

کاربران را می‌توان به روش‌های مختلفی به قسمت‌های گوناگون برنامه هدایت کرد؛ برای مثال با کلیک بر روی المان‌های قابل کلیک HTML و سپس اتصال آن‌ها به کامپوننت‌های برنامه. استفاده‌ی کاربر از bookmark مرورگر و یا ورود مستقیم و دستی آدرس قسمتی از برنامه و یا کلیک بر روی دکمه‌های forward و back مرورگر. تنها مورد اول است که نیاز به تنظیم دارد و سایر قسمت‌ها به صورت خودکار مدیریت خواهند شد. نمونه‌ی آن‌را نیز با تعریف لینک Home پیشتر مشاهده کردید:
  <a [routerLink]="['/home']">Home</a>
- در اینجا با استفاده از یک دایرکتیو ویژه به نام routerLink، المان‌های قابل کلیک HTML را به خاصیت‌های path تعریف شده‌ی در تنظیمات مسیریابی برنامه متصل می‌کنیم. routerLink یک attribute directive است؛ بنابراین می‌توان آن‌را به یک المان قابل کلیک anchor tag، مانند مثال فوق نسبت داد.
- زمانیکه کاربر بر روی این لینک کلیک می‌کند، اولین path متناظر با routerLink یافت شده و فعالسازی خواهد شد.
- علت تعریف مقدار routerLink به صورت [] این است که آرایه‌ی پارامترهای لینک را مشخص می‌کند. بنابراین چون آرایه‌است، نیاز به [] دارد. اولین پارامتر این آرایه مفهوم root URL segment را دارد. در اینجا حتما نیاز است URL segment را با یک / شروع کرد. به علاوه باید دقت داشت که خاصیت path تنظیمات مسیریابی، حساس به حروف کوچک و بزرگ است. بنابراین این مورد را باید در اینجا نیز مدنظر داشت.
- پارامترهای دیگر routerLink می‌توانند مفهوم پارامترهای این segment و یا حتی segments دیگری باشند.

یک نکته: چون در مثال فوق، آرایه‌ی تعریف شده تنها دارای یک عضو است، آن‌را می‌توان به صورت ذیل نیز خلاصه نویسی کرد (one-time binding):
 <a routerLink="/home">Home</a>


تفاوت بین آدرس‌های HTML 5 و Hash-based

زمانیکه مسیریاب Angular کار پردازش آدرس‌های رسیده را انجام می‌دهد، اینکار در سمت کلاینت صورت می‌گیرد و تنها URL segment مدنظر را تغییر داده و این درخواست را به سمت سرور ارسال نمی‌کند. به همین جهت سبب reload صفحه نمی‌شود. دو روش در اینجا جهت مدیریت سمت کلاینت آدرس‌ها قابل استفاده است:
الف) HTML 5 Style
- آدرسی مانند http://localhost:4200/home، یک آدرس به شیوه‌ی HTML 5 است. در اینجا مسیریاب Angular با استفاده از HTML 5 history pushState سبب به روز رسانی History مرورگر شده و آدرس‌ها را بدون ارسال درخواستی به سمت سرور، در همان سمت کلاینت تغییر می‌دهد.
- این روش حالت پیش فرض Angular است و نحوه‌ی نمایش آن بسیار طبیعی به نظر می‌رسد.
- در اینجا URL rewriting سمت سرور نیز جهت هدایت آدرس‌ها، به برنامه‌ی Angular ضروری است. برای مثال زمانیکه کاربری آدرس http://localhost:4200/home را مستقیما در مرورگر وارد می‌کند، این درخواست ابتدا به سمت سرور ارسال خواهد شد و چون چنین صفحه‌ای در سمت سرور وجود ندارد، پیغام خطای 404 را دریافت می‌کند. اینجا است که URL rewriting سمت سرور به فایل index.html برنامه، جهت مدیریت یک چنین حالت‌هایی ضروری است.
برای نمونه اگر از وب سرور IIS استفاده می‌کنید، تنظیم ذیل را به فایل web.config در قسمت system.webServer اضافه کنید (کار کرد آن هم وابسته‌است به نصب و فعالسازی ماژول URL Rewrite بر روی IIS):
<rewrite>
    <rules>
        <rule name="Angular 2+ pushState routing" stopProcessing="true">
            <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                    <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" />
                    <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
                </conditions>
                <action type="Rewrite" url="/index.html" />
        </rule>
    </rules>
</rewrite>

ب) Hash-based
- آدرسی مانند http://localhost:4200/#/home یک آدرس به شیوه‌ی Hash-based بوده و مخصوص مرورگرهایی است بسیار قدیمی که از HTML 5 پشتیبانی نمی‌کنند. اینبار قطعات قرار گرفته‌ی پس از علامت # دارای نام URL fragments بوده و قابلیت پردازش در سمت کلاینت را دارا می‌باشند.
- اگر علاقمند به استفاده‌ی از این روش هستید، نیاز است خاصیت useHash را به true تنظیم کنید:
@NgModule({
   imports: [RouterModule.forRoot(routes, { useHash: true })],
-  این روش نیازی به URL rewriting سمت سرور ندارد. از آنجائیکه سرور هرچیزی را که پس # باشد ندید خواهد گرفت و سعی در یافتن آن‌، در سمت سرور نخواهد کرد.
نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
اگرکلاینتها ویندوزی نباشند چطور؟مثلا برای api ما که با دات نت  و با استفاده از IFormFile  فایل‌های ارسالی رو ذخیره میکنیم، یک کلاینت اندرویدی میتونه فایل ارسال کنه؟ یابایدازاون کلاینت خاص با فرمت base64فقط فایل‌ها رو دریافت کرد؟
مطالب
تهیه یک Clone از مخزن کدی در گوگل کد

برای مثال پروژه "unhaddins" را در نظر بگیرید. این پروژه یک سری افزونه را جهت کار ساده‌تر با NHibernate ارائه داده است. برای مثال چگونه با WPF یا WCF و امثال آن بتوان به سادگی با NHibernate ارتباط برقرار کرد. این پروژه خروجی قابل دریافتی ندارد؛ به عبارتی یک سری سورس کد است. دریافت یک مخزن کد هم که از گوگل کد در این سمت مشکل است ... اما راه بهتری هم وجود دارد. یکی از خواص کار با سورس کنترل‌ها، امکان تهیه یک clone از یک مخزن کد است. تمام پروژه‌های موجود در گوگل کد هم به این شکل با SVN در دسترس هستند:
http://someproject.googlecode.com/svn/trunk/
که به جای someproject ، نام پروژه مورد نظر قرار خواهد گرفت.

برای نمونه، در سایت https://bitbucket.org ثبت نام کنید. سپس گزینه ایجاد یک مخزن جدید را انتخاب کرده:



و در صفحه‌ی باز شده، گزینه‌ی Import from Subversion را انتخاب کنید:



در اینجا Url خواسته شده باید شبیه به همان آدرس trunk فوق باشد و اگر تیک private فعال باشد (که هست)،‌ دیگران امکان دسترسی به مخزن کد شما را نخواهند داشت. البته این تنظیم پس از دریافت، در برگه‌ی Admin مخزن ایجاد شده نیز قابل تغییر است.

به علاوه سایت github.com هم هر چند بر اساس Git کار می‌کند، اما امکان تهیه یک کپی مطابق اصل از یک مخزن کد SVN را هم دارد؛ به شرح زیر:

یک اکانت رایگان در GitHub درست کنید. بعد یک مخزن خالی جدید را ایجاد کرده و در همان صفحه روی لینک Import a Subversion Repository کلیک کنید و آدرس svn مورد نظر را بدهید.

البته GitHub در دریافت پروژه unhaddins موفق عمل نکرد، اما bitbucket خیلی سریع کل آن‌را دریافت نمود.

مطالب
EF Code First #15

EF Code first و بانک‌های اطلاعاتی متفاوت

در آخرین قسمت از سری EF Code first بد نیست نحوه استفاده از بانک‌های اطلاعاتی دیگری را بجز SQL Server نیز بررسی کنیم. در اینجا کلاس‌های مدل و کدهای مورد استفاده نیز همانند قسمت 14 است و تنها به ذکر تفاوت‌ها و نکات مرتبط اکتفاء خواهد شد.


حالت کلی پشتیبانی از بانک‌های اطلاعاتی مختلف توسط EF Code first

EF Code first با کلیه پروایدرهای تهیه شده برای ADO.NET 3.5 که پشتیبانی از EF را لحاظ کرده باشند،‌ به خوبی کار می‌کند. پروایدرهای مخصوص ADO.NET 4.0، تنها سه گزینه DeleteDatabase/CreateDatabase/DatabaseExists را نسبت به نگارش قبلی بیشتر دارند و EF Code first ویژگی‌های بیشتری را طلب نمی‌کند.
بنابراین اگر حین استفاده از پروایدر ADO.NET مخصوص بانک اطلاعاتی خاصی با پیغام «CreateDatabase is not supported by the provider» مواجه شدید، به این معنا است که این پروایدر برای دات نت 4 به روز نشده است. اما به این معنا نیست که با EF Code first کار نمی‌کند. فقط باید یک دیتابیس خالی از پیش تهیه شده را به برنامه معرفی کنید تا مباحث Database Migrations به خوبی کار کنند؛ یا اینکه کلا می‌توانید Database Migrations را خاموش کرده (متد Database.SetInitializer را با پارامتر نال فراخوانی کنید) و فیلدها و جداول را دستی ایجاد کنید.


استفاده از EF Code first با SQLite

برای استفاده از SQLite در دات نت ابتدا نیاز به پروایدر ADO.NET آن است: «مکان دریافت درایور‌های جدید SQLite مخصوص دات نت»
ضمن اینکه به نکته «استفاده از اسمبلی‌های دات نت 2 در یک پروژه دات نت 4» نیز باید دقت داشت.
و یکی از بهترین management studio هایی که برای آن تهیه شده: «SQLite Manager»
پس از دریافت پروایدر آن، ارجاعی را به اسمبلی System.Data.SQLite.dll به برنامه اضافه کنید.
سپس فایل کانفیگ برنامه را به نحو زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Data Source=CodeFirst.db"
providerName="System.Data.SQLite"/>
</connectionStrings>
</configuration>

همانطور که ملاحظه می‌کنید، تفاوت آن با قبل، تغییر connectionString و providerName است.
اکنون اگر همان برنامه قسمت قبل را اجرا کنیم به خطای زیر برخواهیم خورد:
«The given key was not present in the dictionary»
در این مورد هم توضیح داده شد. سه گزینه DeleteDatabase/CreateDatabase/DatabaseExists در پروایدر جاری SQLite برای دات نت وجود ندارد. به همین جهت نیاز است فایل «CodeFirst.db» ذکر شده در کانکشن استرینگ را ابتدا دستی درست کرد.
برای مثال از افزونه SQLite Manager استفاده کنید. ابتدا یک بانک اطلاعاتی خالی را درست کرده و سپس دستورات زیر را بر روی بانک اطلاعاتی اجرا کنید تا دو جدول خالی را ایجاد کند (در برگه Execute sql افزونه SQLite Manager):

CREATE TABLE [Payees](
[Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
[Name] [text] NULL,
[CreatedOn] [datetime] NOT NULL,
[CreatedBy] [text] NULL,
[ModifiedOn] [datetime] NOT NULL,
[ModifiedBy] [text] NULL
);

CREATE TABLE [Bills](
[Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
[Amount] [float](18, 2) NOT NULL,
[Description] [text] NULL,
[CreatedOn] [datetime] NOT NULL,
[CreatedBy] [text] NULL,
[ModifiedOn] [datetime] NOT NULL,
[ModifiedBy] [text] NULL,
[Payee_Id] [integer] NULL
);

سپس سطر زیر را نیز به ابتدای برنامه اضافه کنید:

Database.SetInitializer<Sample09Context>(null);

به این ترتیب database migrations خاموش می‌شود و اکنون برنامه بدون مشکل کار خواهد کرد.
فقط باید به یک سری نکات مانند نوع داده‌ها در بانک‌های اطلاعاتی مختلف دقت داشت. برای مثال integer در اینجا از نوع Int64 است؛ بنابراین در برنامه نیز باید به همین ترتیب تعریف شود تا نگاشت‌ها به درستی انجام شوند.

در کل تنها مشکل پروایدر فعلی SQLite عدم پشتیبانی از مباحث database migrations است. این مورد را خاموش کرده و تغییرات ساختار بانک اطلاعاتی را به صورت دستی به بانک اطلاعاتی اعمال کنید. بدون مشکل کار خواهد کرد.

البته اگر به دنبال پروایدری تجاری با پشتیبانی از آخرین نگارش EF Code first هستید، گزینه زیر نیز مهیا است:
http://devart.com/dotconnect/sqlite/
برای مثال اگر علاقمند به استفاده از حالت تشکیل بانک اطلاعاتی SQLite در حافظه هستید (با رشته اتصالی ویژه Data Source=:memory:;Version=3;New=True;)،‌ فعلا تنها گزینه مهیا استفاده از پروایدر تجاری فوق است؛ زیرا مبحث Database Migrations را به خوبی پشتیبانی می‌کند.



استفاده از EF Code first با SQL Server CE

قبلا در مورد «استفاده از SQL-CE به کمک NHibernate» مطلبی را در این سایت مطالعه کرده‌اید. سه مورد اول آن با EF Code first یکی است و تفاوتی نمی‌کند (یک سری بحث عمومی مشترک است). البته با یک تفاوت؛ در اینجا EF Code first قادر است یک بانک اطلاعاتی خالی SQL Server CE را به صورت خودکار ایجاد کند و نیازی نیست تا آن‌را دستی ایجاد کرد. مباحث database migrations و به روز رسانی خودکار ساختار بانک اطلاعاتی نیز در اینجا پشتیبانی می‌شود.
برای استفاده از آن ابتدا ارجاعی را به اسمبلی System.Data.SqlServerCe.dll قرار گرفته در مسیر Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop اضافه کنید.
سپس رشته اتصالی به بانک اطلاعاتی و providerName را به نحو زیر تغییر دهید:

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Data Source=mydb.sdf;Password=1234;Encrypt Database=True"
providerName="System.Data.SqlServerCE.4.0"/>
</connectionStrings>


بدون نیاز به هیچگونه تغییری در کدهای برنامه، همین مقدار تغییر در تنظیمات ابتدایی برنامه برای کار با SQL Server CE کافی است.
ضمنا مشکلی هم با فیلد Identity در آخرین نگارش EF Code first وجود ندارد؛ برخلاف حالت database first آن که پیشتر این اجازه را نمی‌داد و خطای «Server-generated keys and server-generated values are not supported by SQL Server Compact» را ظاهر می‌کرد.



استفاده از EF Code first با MySQL

برای استفاده از EF Code first با MySQL (نگارش 5 به بعد البته) ابتدا نیاز است پروایدر مخصوص ADO.NET آن‌را دریافت کرد: (^)
که از EF نیز پشتیبانی می‌کند. پس از نصب آن، ارجاعی را به اسمبلی MySql.Data.dll قرار گرفته در مسیر Program Files\MySQL\MySQL Connector Net 6.5.4\Assemblies\v4.0 به پروژه اضافه نمائید.
سپس رشته اتصالی و providerName را به نحو زیر تغییر دهید:

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Datasource=localhost; Database=testdb2; Uid=root; Pwd=123;"
providerName="MySql.Data.MySqlClient"/>
</connectionStrings>

<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider"
invariant="MySql.Data.MySqlClient"
description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</DbProviderFactories>
</system.data>

همانطور که مشاهده می‌کنید در اینجا شماره نگارش دقیق پروایدر مورد استفاده نیز ذکر شده است. برای مثال اگر چندین پروایدر روی سیستم نصب است، با مقدار دهی DbProviderFactories می‌توان از نگارش مخصوصی استفاده کرد.

با این تغییرات پس از اجرای برنامه قسمت قبل، به خطای زیر برخواهیم خورد:
The given key was not present in the dictionary

توضیحات این مورد با قسمت SQLite یکی است؛ به عبارتی نیاز است بانک اطلاعاتی testdb را دستی درست کرد. همچنین جداول و فیلدها را نیز باید دستی ایجاد کرد و database migrations را نیز باید خاموش کرد (پارامتر Database.SetInitializer را به نال مقدار دهی کنید).
برای این منظور یک دیتابیس خالی را ایجاد کرده و سپس دو جدول زیر را به آن اضافه کنید:

CREATE TABLE IF NOT EXISTS `bills` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Amount` float DEFAULT NULL,
`Description` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`CreatedOn` datetime NOT NULL,
`CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`ModifiedOn` datetime NOT NULL,
`ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`Payee_Id` int(11) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `payees` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`CreatedOn` datetime NOT NULL,
`CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`ModifiedOn` datetime NOT NULL,
`ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

پس از این تغییرات، برنامه بدون مشکل اجرا خواهد شد (ایجاد بانک اطلاعاتی خالی به همراه ایجاد ساختار جداول و خاموش کردن database migrations که توسط این پروایدر پشتیبانی نمی‌شود).

به علاوه پروایدر تجاری دیگری هم در سایت devart.com برای MySQL و EF Code first مهیا است که مباحث database migrations را به خوبی مدیریت می‌کند.


مشکل!
اگر به همین نحو برنامه را اجرا کنیم، فیلدهای یونیکد فارسی ثبت شده در MySQL با «??????? ?? ????» مقدار دهی خواهند شد و تنظیم CHARACTER SET utf8 COLLATE utf8_persian_ci نیز کافی نبوده است (این مورد با SQLite یا نگارش‌های مختلف SQL Server بدون مشکل کار می‌کند و نیاز به تنظیم اضافه‌تری ندارد):

ALTER TABLE `bills` DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci

برای رفع این مشکل توصیه شده است که CharSet=UTF8 را به رشته اتصالی به بانک اطلاعاتی اضافه کنیم. اما در این حالت خطای زیر ظاهر می‌شود:
The provider did not return a ProviderManifestToken string
این مورد فقط به اشتباه بودن تعاریف رشته اتصالی بر می‌گردد؛‌ یا عدم پشتیبانی از تنظیم اضافه‌ای که در رشته اتصالی ذکر شده است.
مقدار صحیح آن دقیقا مساوی CHARSET=utf8 است (با همین نگارش و رعایت کوچکی و بزرگی حروف؛ مهم!):

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Datasource=localhost; Database=testdb; Uid=root; Pwd=123;CHARSET=utf8"
providerName="MySql.Data.MySqlClient"/>
</connectionStrings>


به این ترتیب، مشکل ثبت عبارات یونیکد فارسی برطرف می‌شود (البته جدول هم بهتر است به DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci تغییر پیدا کند؛ مطابق دستور Alter ایی که در بالا ذکر شد).

مطالب
زیر نویس فارسی ویدیوهای ساخت برنامه‌های مترو توسط سی شارپ و XAML - قسمت آخر

زیرنویس‌های فارسی قسمت آخر «Building Windows 8 Metro Apps in C# and XAML» را از اینجا می‌تونید دریافت کنید.

این قسمت برای کسانی که می‌خواهند مروری بر مفاهیم Binding موجود در WPF و سیلورلایت داشته باشند و همچنین تفاوت‌های آن‌را با نمونه موجود در WinRT بررسی کنند، بسیار مفید است. در کل سیستم Binding موجود در WinRT یک نمونه ساده شده آنچیزی است که در سیلورلایت موجود است (و البته سیلورلایت هم یک نمونه ساده شده WPF است!).
برای مثال خاصیت UpdateSourceTrigger موجود در عبارات Binding مرتبط با WPF و سیلورلایت فعلا در WinRT وجود ندارد.
تفاوت دیگر این است که هرچند اینترفیس INotifyPropertyChanged در WinRT هم وجود دارد اما نمونه مهیای در فضای نام جدیدی به نام windows.ui.xaml.data توسط WinRT شناسایی می‌شود و اگر کدهای سایر فناوری‌های مشابه را به سیستم مترو تبدیل کنید، کار نخواهند کرد! چون این اینترفیس پیشتر در فضای نام System.ComponentModel تعریف شده بود و هنوز هم حضور دارد (فقط در حد یک تغییر سطر تعاریف فضاهای نام کفایت می‌کند).
یک تله دیگر هم در WinRT وجود دارد. در اینجا هم کلاسی به نام DependencyObject وجود دارد که ... معادل نمونه مشابه WPF و Silverlight نیست و XAML امکان تشخیص خودکار تغییرات خواص آن‌را ندارد.
همچنین اینترفیس INotifyCollectionChanged مرتبط با ObservableCollection موجود در WPF و Silverlight در WinRT فعلا وجود خارجی ندارند (هرچند هنوز در خود دات نت فریم ورک وجود دارند، اما کار نمی‌کنند). به نظر قرار است تا قبل از ارائه نهایی ویندوز 8 در این مورد تصمیم گیری شود. اما در اینجا باید از IObservableVector استفاده کرد! (کلا این کلمه Vector ابراز وجود زاید طراحان سی++ مترو است! یعنی ما هنوز هم زنده‌ایم و سی++ هنوز هم مهم است!)
نحوه گروه بندی اطلاعات نیز تغییر کرده است و باید منبع داده‌ای، اینترفیس جدید IGroupInfo را پیاده سازی کند. به علاوه CollectionViewSource آن نیز فعلا قابلیت‌های جستجو و مرتب سازی موجود در WPF و سیلورلایت را ارائه نمی‌دهد.

پروژه‌ها
فروشگاه اینترنتی (الکترونیک) با معماری سه لایه
دانلود پروژه فروشگاه اینترنتی با معماری سه لایه

این پروژه به کمک امکانات شیرین C#‎.NET, ASP.NET,jQuery در محیط Visual Studio 2010 طراحی و پیاده سازی شده است.
ممکن است در این برنامه به اشکالاتی برخورد کنید چون یک پروژه کاری بوده که در طی یک کلاس درس تکمیل شده و هم اکنون در اختیار شما قرار می‌گیرد لطفا در صورت بروز مشکل از طریق بازخورد مطرح فرمایید و از ارسال پیام خصوصی خودداری نمایید.
پروژه با معماری سه لایه طراحی و پیاده سازی شده است
 که شامل لایه DAL برای ارتباط با بانک
 BLL برای اعمال قوانین تجاری
و لایه نمایش آن که ASP.NET استفاده شده است
 بانک اطلاعاتی برنامه با SQL SERVER 2008 می‌باشد که می‌توانید آن را تغییر داده و از بانک اطلاعاتی دلخواه خود استفاده کنید.
فایل اسکریپت نیز در کنار پروژه برای نسخه‌های دیگر SQL قرار گرفته است.
 لازم به ذکر است که برنامه ProviderBase نیز می‌باشد.
یعنی شما میتوانید بانک دلخواه خود را استفاده نموده و بدون کوچکترین تغییری در برنامه سایت از آن استفاده نمایید فقط کافی است که در فایل web.config مشخصات آن Provider را ثبت نموده و کدهای مربوط به Provider خود را بنویسید.
برای استفاده ابتدا دیتابیس را در SQL خود Attach کرده و در فایل web.config در قسمت تنظیمات ConnectionString تنظیمات سرور و نام دیتابیس را وارد نمایید.

این پروژه دارای امکانات
  • فروشگاه اینترنتی با قابلیت گروه بندی محصولات
  • ذخیره تصاویر در دیتابیس و بازیابی با یک Handler
  • سرویس اعلانات سایت
  • درج کلمات کلیدی هر صفحه به صورت اتوماتیک و با توجه به محتوای صفحه
  • استفاده از قابلیت Profile برای کاربران
  • کارت خرید و ذخیره آن در پروفایل کاربر
  • ثبت سوابق خرید کاربر
  • ثبت فیش‌های پرداختی کاربر
  • ثبت نام کاربران جدید در سایت
  • ثبت، نمایش و مدیریت سخنان قصار در نرم افزار
  • امکان Cache کردن اطلاعات دریافتی از بانک و مدیریت Cache 
  • پیاده سازی تمامی کویری‌های بان با Store Procedure در SQL Server
  • استفاده از سرویس Membership
  • خواندن تنظیمات برنامه از فایل Web.Config و معادل‌سازی آن با کلاس‌های مربوطه
  • و ...
البته شاید کامل نباشد اما به عنوان یک منبع آموزشی در زمینه برنامه نویسی سه لایه و بعضی ایده‌های خاص ممکن است برای دوستان مبتدی و متوسطه در زمینه طراحی سایت مفید واقع شود.
در صورت لزوم می‌توانید به لینک اصلی این پروژه در آدرس سایت ما مراجعه نمایید.
نام کاربری و کلمه عبور مدیریت سایت:
UserName: Admin
Password: 1234567@
----------
UserName: Saber_Fatholahi
Password: 1234567@