پاسخ به بازخورد‌های پروژه‌ها
درخواست ایده برای برای پیاده سازی منوی چند سطحی
با همین یک جدول خود ارجاع هم میشه همین کار را کرد.
میشه به صورت ترکیبی از SQL Antipattern شمارش مسیر در کنار این جدول خود ارجاع استفاده کرد به این شکل هم ساختار درختی را دارین هم با توجه به عمق محدود گروه بندی‌ها ، روش شمارش مسیر هم برای کوئری‌ها مربوط به درخت خیلی مفید خواهد بود. 
همچنین در رابطه با بحث جدیدی که مطرح کردید هم میتوانید هنگام درج کالایی ، گروه‌های سطح آخر را اجازه انتخاب دهید. و حالا در سطح اول هم  با امکان شمارش مسیری که گذاشته ایم ، مثلا خیلی راحت پرفروش‌ترین کالاهای آن را  که در شاخه‌های زیرین ثبت شده اند بدست آورد.
این مورد محدود کردن به انتخاب گروه‌های موجود در شاخه  در هنگام درج یکسری  خصوصیات داینامیک (Specification Attribute)  در سیستم باید در سمت برنامه کنترل شود .
بازخوردهای پروژه‌ها
تغییر برنامه به عنوان سرویس ویندوز
سلام 
با توجه به اینکه این برنامه که زحمت تهیه‌اش رو کشیدید بسیار برای ما ایرانیان مفید هست اگر به صورت یک ویندوز سرویس ارائه بشه بسیار کاربردی‌تر خواهد بود ... بطور مثال زمان استفاده از این برنامه اگر نرم افزارهایی برای بهبود کارایی ویندوز مثل TuneUp Utilities روی سیستم نصب باشه اجرای خودکار برنامه برای بهینه سازی سرعت لود ویندوز متوقف میشه و کار نمیکنه و اکثر مواقع به طور خودکار (بسته به تنظیمات) نرم افزارهای غیر ضرروری غیر فعال میشوند... ولی سرویس‌های ویندوز به چنین مشکلاتی بر نمیخورند عموما ... 
پیشنهاد من تبدیل برنامه به یک ویندوز سرویس بود .
با تشکر
موفق باشید
پروژه‌ها
سیستم مدیریت محتوای IRIS
IRIS یک سیستم مدیریت محتوای تهیه شده به کمک ASP.NET MVC 4 است که مدتی پیش برای یادگیری خودم نوشتم. به نظرم به درد خیلی از تازه کارها می‌خوره و برایشان مفید خواهد بود؛ امیدوارم!
در حقیقت بنده قصد راه اندازی یک سایت برای میزبانی کتاب‌های تخصصی کامپیوتر را داشتم که این CMS را نوشتم. اون سایت هم تا الان به خوبی کار می‌کنه و مشکلی برایش پیش نیامده است.
اگر قصد دیدن نمونه ای از این سیستم را دارید، می‌توانید به سایت زیر مراجعه کنید:
برای راه اندازی پروژه از این مقاله کمک بگیرید.
نام کاربری: admin
کلمه عبور: 123456
به روز رسانی:
پروژه به NET 5. و EF Core 5 و ASP.NET Core 5 بروزرسانی شد.
مسیرراه‌ها
WPF
          مطالب
          مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت هفتم - کار با سرویس‌های متفاوتی با یک امضاء
          فرض کنید قرارداد IService را تدارک دیده‌اید و بر اساس آن سرویس‌های A و B را به سیستم تزریق وابستگی‌های برنامه‌های NET Core. تزریق کرده‌اید (برای مثال در برنامه دو DbContext را تعریف کرده‌اید و یک اینترفیس IUnitOfWork را دارید). اکنون اگر از این سیستم، یک پیاده سازی IService را درخواست کنید، چه اتفاقی رخ می‌دهد؟ در حالت معمول آن، آخرین سرویسی را که ثبت کرده‌اید، یعنی وهله‌ای از سرویس B را بازگشت خواهد داد. در ادامه قصد داریم با این قابلیت امکان ثبت چندین سرویس مشتق شده‌ی از یک اینترفیس، بیشتر آشنا شویم.


          ثبت پیاده سازی‌های متعدد یک اینترفیس در سیستم تزریق وابستگی‌های NET Core.

          داشتن چندین پیاده سازی از یک اینترفیس، شاید یکی از اهداف اصلی وجود آن‌ها باشد. برای نمونه قرار داد ارسال پیامی را در برنامه، مانند IMessageService به صورت زیر طراحی و سپس بر اساس آن، دو پیاده سازی ارسال پیام‌ها را از طریق ایمیل و SMS، اضافه می‌کنیم:
          namespace CoreIocServices
          {
              public interface IMessageService
              {
                  void Send(string message);
              }
          
              public class EmailService : IMessageService
              {
                  public void Send(string message)
                  {
                      // ...
                  }
              }
          
              public class SmsService : IMessageService
              {
                  public void Send(string message)
                  {
                      //todo: ...
                  }
              }
          }
          در ادامه برای معرفی آن‌ها به سیستم تزریق وابستگی‌های NET Core.، به نحو متداول زیر عمل خواهیم کرد و هر دوی این پیاده سازی‌ها را بر اساس اینترفیس آن‌ها معرفی می‌کنیم:
          namespace CoreIocSample02
          {
              public class Startup
              {
                  public void ConfigureServices(IServiceCollection services)
                  {
                      services.AddTransient<IMessageService, EmailService>();
                      services.AddTransient<IMessageService, SmsService>();
                  }
          در این حالت اگر این سرویس‌ها را به صورت زیر به یک کنترلر تزریق کنیم، انتظار دریافت کدام سرویس‌ها را خواهید داشت؟
          using CoreIocServices;
          using Microsoft.AspNetCore.Mvc;
          
          namespace CoreIocSample02.Controllers
          {
              public class MessagesController : Controller
              {
                  private readonly IMessageService _emailService;
                  private readonly IMessageService _smsService;
          
                  public MessagesController(IMessageService emailService, IMessageService smsService)
                  {
                      _emailService = emailService;
                      _smsService = smsService;
                  }
              }
          }
          در این حالت اگر بر روی سازنده‌ی این کنترلر یک break-point را قرار دهیم، پارامترهای تزریق شده‌ی در سازنده‌ی کلاس به صورت زیر خواهند بود:


          همانطور که مشاهده می‌کنید، هر دو پارامتر، با وهله‌ای از آخرین سرویس معرفی شده‌ی از نوع IMessageService مقدار دهی شده‌اند؛ یعنی SmsService در اینجا. در ادامه روش‌های مختلفی را برای رفع این مشکل بررسی می‌کنیم.


          روش اول: سیستم تزریق وابستگی‌های NET Core. از سازنده‌های IEnumerable پشتیبانی می‌کند

          برای مدیریت دریافت سرویس‌هایی که از یک اینترفیس مشتق شده‌اند، خود NET Core. بدون نیاز به تنظیمات اضافه‌تری، روش تزریق IEnumerableها را در سازنده‌های کلاس‌ها پشتیبانی می‌کند:
          using System.Collections.Generic;
          using CoreIocServices;
          using Microsoft.AspNetCore.Mvc;
          
          namespace CoreIocSample02.Controllers
          {
              public class MessagesController : Controller
              {
                  private readonly IEnumerable<IMessageService> _messageServices;
          
                  public MessagesController(IEnumerable<IMessageService> messageServices)
                  {
                      _messageServices = messageServices;
                  }
          در اینجا نیز اگر بر روی سازنده‌ی این کنترلر یک break-point را قرار دهیم، پارامتر تزریق شده‌ی در سازنده‌ی کلاس به صورت زیر خواهد بود:


          به این ترتیب لیست وهله‌های تمام سرویس‌هایی از نوع IMessageService در اختیار ما قرار گرفته‌است و برای اهدافی مانند پیاده سازی الگوهایی در رده‌ی chain of responsibility و یا الگوی استراتژی، مفید است. در این حالت اگر نیاز به سرویس از نوع خاصی وجود داشت، می‌توان از روش زیر استفاده کرد:
          var emailService = _messageServices.OfType<EmailService>().First();
          و یا اگر از روش Service Locator استفاده می‌کنید، serviceProvider به همراه متد ویژه‌ی GetServices که لیست تمام سرویس‌هایی از نوعی خاص را بر می‌گرداند نیز هست:
          var messageServices = serviceProvider.GetServices<IMessageService>();


          روش دوم: دریافت شرطی وهله‌های مورد نیاز با «دخالت در مراحل وهله سازی اشیاء توسط IoC Container»

          روش «دخالت در مراحل وهله سازی اشیاء توسط IoC Container» را در قسمت قبل بررسی کردیم. یکی دیگر از مثال‌هایی را که در این مورد می‌توان ارائه داد، شرطی کردن بازگشت وهله‌ها است که برای سناریوی مطلب جاری بسیار مفید است:
          الف) برای شرطی کردن بازگشت وهله‌ها، بهتر است این شرط را بجای رشته‌ها و یا اعدادی خاص، بر اساس یک enum مشخص انجام دهیم:
          namespace CoreIocServices
          {
              public enum MessageServiceType
              {
                  EmailService,
                  SmsService
              }
          در اینجا یک enum جدید را تعریف کرده‌ایم تا مقایسه‌ها را در زمان بازگشت سرویسی خاص، بر اساس اعضای مشخص آن انجام دهیم.

          ب) سپس هر دو سرویس مشتق شده‌ی از یک اینترفیس را به صورت معمولی ثبت می‌کنیم:
          namespace CoreIocSample02
          {
              public class Startup
              {
                  public void ConfigureServices(IServiceCollection services)
                  {
                      services.AddTransient<EmailService>();
                      services.AddTransient<SmsService>();
          اینکار سبب خواهد شد تا بتوانیم در حین بررسی شرط‌های رسیده، برای مثال دستور ()<serviceProvider.GetService<EmailService را صادر کنیم.

          ج) اکنون می‌توان مرحله‌ی شرطی کردن بازگشت این وهله‌ها را به صورت زیر، با دخالت در مراحل وهله سازی اشیاء، پیاده سازی کرد:
          namespace CoreIocSample02
          {
              public class Startup
              {
                  public void ConfigureServices(IServiceCollection services)
                  {
                      services.AddTransient<EmailService>();
                      services.AddTransient<SmsService>();
                      services.AddTransient<Func<MessageServiceType, IMessageService>>(serviceProvider => key =>
                      {
                          switch (key)
                          {
                              case MessageServiceType.EmailService:
                                  return serviceProvider.GetRequiredService<EmailService>();
                              case MessageServiceType.SmsService:
                                  return serviceProvider.GetRequiredService<SmsService>();
                              default:
                                  throw new NotImplementedException($"Service of type {key} is not implemented.");
                          }
                      });
          در اینجا پارامتر ارسالی به متد AddTransient را از نوع <Func<MessageServiceType, IMessageService انتخاب کرده‌ایم. این مورد نیز دقیقا مانند «مثال 2: وهله سازی در صورت نیاز وابستگی‌های یک سرویس به کمک Lazy loading» قسمت قبل عمل می‌کند. یعنی تا زمانیکه این Func، در قسمتی از کدهای برنامه فراخوانی نشود، سرویسی را نیز بازگشت نخواهد داد.

          د) مرحله‌ی آخر کار، روش استفاده‌ی از این امضایء ویژه‌ی Lazy load شده‌است:
          namespace CoreIocSample02.Controllers
          {
              public class MessagesController : Controller
              {
                  private readonly Func<MessageServiceType, IMessageService> _messageServiceResolver;
          
                  public MessagesController(Func<MessageServiceType, IMessageService> messageServiceResolver)
                  {
                      _messageServiceResolver = messageServiceResolver;
                  }
          دقیقا همان امضای Func ای را که در متد AddTransient معرفی تنظیمات تزریق وابستگی‌ها تعریف کردیم، به سازنده‌ی یک کنترلر تزریق می‌کنیم. تا اینجای کار هنوز هیچکدام از سرویس‌های از نوع IMessageService وهله سازی نشده‌اند (روش دیگر پیاده سازی وهله سازی با تاخیر و در صورت نیاز). اکنون برای وهله سازی آن‌ها باید به صورت زیر عمل کرد:
          public IActionResult Index()
          {
             var emailService = _messageServiceResolver(MessageServiceType.EmailService);
             //use emailService ...
             return View();
          }

          کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-07.zip
          مطالب
          آزمایش Web APIs توسط Postman - قسمت سوم - نکات تکمیلی ایجاد درخواست‌ها
          تا اینجا، یک دید کلی را نسبت به postman و توانمندی‌های آن پیدا کرده‌ایم. در ادامه قصد داریم، تعدادی نکته‌ی تکمیلی مهم را که در حین ساخت درخواست‌ها در postman، به آن‌ها نیاز پیدا خواهیم کرد، بررسی کنیم.


          ایجاد یک request bin جدید

          برای مشاهده‌ی محتوای ارسالی توسط postman بدون برپایی وب سرویس خاصی، از سایت requestbin استفاده خواهیم کرد. در اینجا با کلیک بر روی دکمه‌ی create a request bin، یک آدرس موقتی را مانند http://requestbin.fullcontact.com/1gdduy21 برای شما تولید می‌کند. می‌توان انواع و اقسام درخواست‌ها را به این آدرس ارسال کرد و سپس با ریفرش کردن آن در مرورگر، دقیقا محتوای ارسالی به سمت سرور را بررسی نمود.
          برای مثال اگر همین آدرس را در postman وارد کرده و سپس بر روی دکمه‌ی send آن کلیک کنیم، پس از ریفرش صفحه، چنین تصویری حاصل می‌شود:



          Encoding کوئری استرینگ‌ها در postman

          مثال زیر را درنظر بگیرید:


          در اینجا با استفاده از گرید ساخت Query Params، دو کوئری استرینگ جدید را ایجاد کرده‌ایم که در دومی، مقدار وارد شده، دارای فاصله است. اگر این درخواست را ارسال کنیم، مشاهده خواهیم کرد که مقدار ارسالی توسط آن encoded نیست:


          برای رفع این مشکل می‌توان بر روی تکست‌باکس ورود مقدار یک کلید، کلیک راست کرد و از منوی باز شده، گزینه‌ی encode URI component را انتخاب نمود:


          البته برای اینکه این گزینه درست عمل کند، نیاز است یکبار کل متن را انتخاب کرد و سپس بر روی آن کلیک راست نمود، تا انتخاب گزینه‌ی encode URI component، به درستی اعمال شود:



          امکان تعریف متغیرها در آدرس‌های HTTP

          Postman از امکان تعریف path variables پشتیبانی می‌کند. برای مثال مسیر api.example.com/users/5/contracts/2 را در نظر بگیرید که می‌تواند سبب نمایش اطلاعات قرارداد دوم کاربر پنجم شود. برای پویا کردن یک چنین آدرسی در postman می‌توان از مسیری مانند api.example.com/users/:userid/contracts/:contractid استفاده کرد:


          اگر به تصویر فوق دقت کنیم، متغیرهای شروع شده‌ی با :، در قسمت path variables ذکر شده‌اند و به سادگی قابل تغییر و مقدار دهی می‌باشند (در گریدی همانند گرید کوئری استرینگ‌ها) که برای آزمایش دستی، بسیار مفید هستند.


          امکان ارسال فایل‌ها به سمت سرور

          زمانیکه برای مثال، نوع درخواست به Post تغییر می‌کند، امکان تنظیم بدنه‌ی آن نیز مسیر می‌شود. در این حالت اگر گزینه‌ی form-data را انتخاب کنیم، با نزدیک کردن اشاره‌گر ماوس به هر ردیف جدید (پیش از ورود کلید آن ردیف)، می‌توان نوع Text و یا File را انتخاب کرد:


          در اینجا پس از انتخاب گزینه‌ی File، می‌توان علاوه بر تعیین کلید این ردیف، با استفاده از دکمه‌ی select files، چندین فایل را نیز برای ارسال انتخاب کرد:



          روش انتقال درخواست‌های پیچیده به Postman

          تا اینجا روش ساخت درخواست‌های متداولی را بررسی کردیم که آنچنان پیچیده، طولانی و به همراه جزئیات زیادی (مانند کوکی‌ها،انواع و اقسام هدرها و ...) نبودند. فرض کنید می‌خواهید درخواست ارسال یک امتیاز جدید را به مطلبی در سایت جاری، توسط Postman شبیه سازی کنیم. برای اینکار، توسط مرورگر کروم به سایت وارد شده و پس از لاگین و تنظیم خودکار کوکی‌های اعتبارسنجی، برگه‌ی developer tools مرورگر را باز کرده و در آن، قسمت network را انتخاب کنید:


          در اینجا گزینه‌ی preserve log را نیز انتخاب کنید تا پس از ارسال درخواستی، سابقه‌ی عملیات، پاک نشود. سپس به صورت معمولی به مطلبی امتیاز دهید. اکنون بر روی مدخل درخواست آن کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی Copy->Copy as cURL را انتخاب کنید تا این عملیات و تمام جزئیات مرتبط با آن‌را تبدیل به یک دستور cURL کرده و به حافظه کپی کند.
          سپس در postman، از منوی بالای صفحه، سمت چپ آن، گزینه‌ی Import را انتخاب کنید:


          در ادامه این دستور کپی شده‌ی در حافظه را در قسمت Paste Raw Text، قرار دهید و بر روی دکمه‌ی import کلیک کنید:


          در این حالت postman این دستور را پردازش کرده و فیلدهای ساخت یک درخواست را به صورت خودکار پر می‌کند.

          یک نکته‌ی مهم: در حالت انتخاب Copy->Copy as cURL در مرورگر کروم، دو گزینه‌ی cmd و bash موجود هستند. حالت bash را باید انتخاب کنید تا توسط postman به دسترسی parse شود. حالت cmd یک چنین خروجی مشکل داری را در postman تولید می‌کند که قابل ارسال به سمت سرور نیست:


          اما جزئیات حالت bash آن، به درستی parse شده‌است و قابلیت send مجدد را دارد:



          کار با کوکی‌ها در Postman

          کوکی‌ها نیز در اصل به صورت یک هدر HTTP به صورت خودکار توسط مرورگرها به سمت سرور ارسال می‌شوند؛ اما Postman روش ساده‌تری را برای تعریف و کار با آن‌ها ارائه می‌دهد (و ترجیح می‌دهد که بین کوکی‌ها و هدرها تفاوت قائل شود؛ هم در زمان ارسال و هم در زمان نمایش response که در کنار قسمت هدرهای دریافتی از سرور، لیست کوکی‌های دریافتی نیز به صورت مجزایی نمایش داده می‌شوند).


          با کلیک بر روی لینک Cookies، در ذیل قسمتی که آدرس یک درخواست تنظیم می‌شود، قسمت مدیریت کوکی‌ها نیز ظاهر خواهد شد که با انتخاب هر نامی در اینجا، می‌توان مقدار آن‌را ویرایش و یا حتی حذف کرد. در این لیست، تمام کوکی‌هایی را که تاکنون تنظیم کرده باشید، می‌توانید مشاهده کنید (و مختص به برگه‌ی خاصی نیست) و همانند قسمت مدیریت کوکی‌های یک مرورگر رفتار می‌کند.
          روش کار با آن نیز به این صورت است: ابتدا باید یک دومین را اضافه کنید. سپس ذیل دومین اضافه شده، دکمه‌ی Add cookie ظاهر می‌شود و به هر تعدادی که لازم باشد، می‌توان برای آن دومین کوکی تعریف کرد:


          پس از تعریف کوکی‌ها، در حین کلیک بر روی دکمه‌ی send، کوکی‌های متعلق به دومین وارد شده‌ی در قسمت آدرس درخواست، از قسمت مدیریت کوکی‌ها به صورت خودکار دریافت شده و به سمت سرور ارسال خواهند شد.


          به اشتراک گذاری سابقه‌ی درخواست‌ها

          در قسمت اول مشاهده کردیم که برای ذخیره سازی درخواست‌ها، باید آن‌ها را در مجموعه‌های Postman، ذخیره و ساماندهی کرد. برای گرفتن خروجی از این مجموعه‌ها و به اشتراک گذاشتن آن‌ها، اگر اشاره‌گر ماوس را بر روی نام یک مجموعه حرکت دهیم، یک دکمه‌ی جدید با برچسب ... ظاهر می‌شود:


          با کلیک بر روی آن، یکی از گزینه‌های آن، export نام دارد که جزئیات تمام درخواست‌های یک مجموعه را به صورت یک فایل JSON ذخیره می‌کند. برای نمونه فایل JSON خروجی دو قسمت قبل این سری را می‌توانید از اینجا دریافت کنید: httpbin.postman_collection.json
          پس از تولید این فایل JSON، برای بازیابی آن می‌توان از دکمه‌ی Import که در منوی سمت چپ، بالای postman قرار دارد، استفاده کرد که نمونه‌ای از آن‌را برای Import جزئیات درخواست‌های cURL پیشتر مشاهده کردید. در اینجا، همان اولین گزینه‌ی دیالوگ Import که Import file نام دارد، دقیقا برای همین منظور تدارک دیده شده‌است.
          مطالب
          Count یا Any

          با وجود امکانات مهیای توسط LINQ ، یک سری از عادات متداول حین کار با گروهی از اشیاء باید کنار گذاشته شوند؛ برای مثال چگونگی بررسی این مطلب که آیا شیء IEnumerable ما حاوی عنصری هست یا خیر.
          روش متداول انجام اینکار استفاده از متد Count است. چون این متد پیش از تدارک امکانات LINQ نیز وجود داشته، بنابراین اولین موردی که جهت بررسی آن به ذهن خطور می‌کند، استفاده از متد Count می‌باشد؛ برای مثال:
          void Method(IEnumerable<Status> statuses)
          {
          if (statuses != null && statuses.Count() > 0)
          // do something...
          }
          این روش بهینه نیست زیرا کار متد Count بررسی تک تک عناصر شیء IEnumerable و سپس بازگرداندن تعداد آن‌ها است. این مورد خصوصا در حالت‌های کار با بانک اطلاعاتی و تنظیمات lazy-loading آن و یا تعداد بالای عناصر یک لیست، بسیار هزینه‌بر خواهد شد.
          ولی در اینجا هدف ما این است که آیا شیء IEnumerable دارای حداقل یک عنصر است یا خیر؟ بنابراین بجای استفاده از متد Count بهتر است از یکی از extension methods فراهم شده توسط LINQ به نام Any استفاده شود.
          کار متد Any ، پس از بررسی اولین عنصر یک مجموعه، خاتمه خواهد یافت و بدیهی است که نسبت به متد Count بسیار سریعتر و کم هزینه‌تر خواهد بود. علاوه بر آن حین کار با بانک‌های اطلاعاتی برای مثال توسط LINQ to Entities ، در SQL نهایی تولیدی به EXISTS ترجمه خواهد شد.
          void Method(IEnumerable<Status> statuses)
          {
          if (statuses != null && statuses.Any())
          // do something...
          }
          خلاصه‌ی بحث:
          از این پس حین استفاده از انواع و اقسام لیست‌ها، آرایه‌ها، IEnumerable ها و امثال آن‌ها، جهت بررسی خالی بودن یا نبودن آن‌ها تنها از متد Any فراهم شده توسط LINQ استفاده نمائید.
          if (myArray != null && myArray.Any())
          // do something...

          مطالب
          اعتبارسنجی از راه دور در فرم‌های مبتنی بر قالب‌های Angular
          در پروژه angular2-validations، یک نمونه پیاده سازی اعتبارسنجی از راه دور یا RemoteValidation را می‌توانید مشاهده کنید. این پیاده سازی مبتنی بر Promiseها است. در مطلب جاری پیاده سازی دیگری را بر اساس Observableها مشاهده خواهید کرد و همچنین ساختار آن شبیه به ساختار remote validation در ASP.NET MVC و jQuery Validator طراحی شده‌است.


          نگاهی به ساختار طراحی اعتبارسنجی از راه دور در ASP.NET MVC و jQuery Validator

          در نگارش‌های مختلف ASP.NET MVC و ASP.NET Core، ویژگی Remote سمت سرور، سبب درج یک چنین ویژگی‌هایی در سمت کلاینت می‌شود:
          data-val-remote="کلمه عبور وارد شده را راحت می&zwnj;توان حدس زد!" 
          data-val-remote-additionalfields="*.Password1" 
          data-val-remote-type="POST" 
          data-val-remote-url="/register/checkpassword"
          که شامل موارد ذیل است:
          - متن نمایشی خطای اعتبارسنجی.
          - تعدادی فیلد اضافی که در صورت نیز از فرم استخراج می‌شوند و به سمت سرور ارسال خواهند شد.
          - نوع روش ارسال اطلاعات به سمت سرور.
          - یک URL که مشخص می‌کند، این اطلاعات باید به کدام اکشن متد در سمت سرور ارسال شوند.

          سمت سرور هم می‌تواند یک true یا false را بازگشت دهد و مشخص کند که آیا اطلاعات مدنظر معتبر نیستند یا هستند.
          شبیه به یک چنین ساختاری را در ادامه با ایجاد یک دایرکتیو سفارشی اعتبارسنجی برنامه‌های Angular تدارک خواهیم دید.


          ساختار اعتبارسنج‌های سفارشی async در Angular

          در مطلب «نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular» جزئیات نوشتن اعتبارسنج‌های متداول فرم‌های Angular را بررسی کردیم. این نوع اعتبارسنج‌ها چون اطلاعاتی را به صورت Ajax ایی به سمت سرور ارسال نمی‌کنند، با پیاده سازی اینترفیس Validator تهیه خواهند شد:
           export class EmailValidatorDirective implements Validator {
          اما زمانیکه نیاز است اطلاعاتی مانند نام کاربری یا ایمیل او را به سرور ارسال کنیم و در سمت سرور، پس از جستجوی در بانک اطلاعاتی، منحصربفرد بودن آن‌ها مشخص شود یا خیر، دیگر این روش همزمان پاسخگو نخواهد بود. به همین جهت اینبار اینترفیس دیگری به نام AsyncValidator برای انجام اعمال async و Ajax ایی در Angular تدارک دیده شده‌است:
           export class RemoteValidatorDirective implements AsyncValidator {
          در این حالت امضای متد validate این اینترفیس به صورت ذیل است:
          validate(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
          یعنی در اینجا هم می‌توان یک Promise را بازگشت داد (مانند پیاده سازی که در ابتدای بحث عنوان شد) و یا می‌توان یک Observable را بازگشت داد که در ادامه نمونه‌ای از پیاده سازی این روش دوم را بررسی می‌کنیم؛ چون امکانات بیشتری را نسبت به Promiseها به همراه دارد. برای مثال در اینجا می‌توان اندکی صبر کرد تا کاربر تعدادی حرف را وارد کند و سپس این اطلاعات را به سرور ارسال کرد. به این ترتیب ترافیک ارسالی به سمت سرور کاهش پیدا می‌کند.


          پیاده سازی یک اعتبارسنج از راه دور مبتنی بر Observableها در Angular

          ابتدا یک دایرکتیو جدید را به نام RemoteValidator به ماژول custom-validators اضافه کرده‌ایم:
           >ng g d CustomValidators/RemoteValidator -m custom-validators.module
          در ادامه کدهای کامل این اعتبارسنج را مشاهده می‌کنید:
          import { Directive, Input } from "@angular/core";
          import {
            AsyncValidator,
            AbstractControl,
            NG_ASYNC_VALIDATORS
          } from "@angular/forms";
          import { Http, RequestOptions, Response, Headers } from "@angular/http";
          import "rxjs/add/operator/map";
          import "rxjs/add/operator/distinctUntilChanged";
          import "rxjs/add/operator/takeUntil";
          import "rxjs/add/operator/take";
          import { Observable } from "rxjs/Observable";
          import { Subject } from "rxjs/Subject";
          
          @Directive({
            selector:
              "[appRemoteValidator][formControlName],[appRemoteValidator][formControl],[appRemoteValidator][ngModel]",
            providers: [
              {
                provide: NG_ASYNC_VALIDATORS,
                useExisting: RemoteValidatorDirective,
                multi: true
              }
            ]
          })
          export class RemoteValidatorDirective implements AsyncValidator {
            @Input("remote-url") remoteUrl: string;
            @Input("remote-field") remoteField: string;
            @Input("remote-additional-fields") remoteAdditionalFields: string;
          
            constructor(private http: Http) {}
          
            validate(control: AbstractControl): Observable<{ [key: string]: any }> {
              if (!this.remoteUrl || this.remoteUrl === undefined) {
                return Observable.throw("`remoteUrl` is undefined.");
              }
          
              if (!this.remoteField || this.remoteField === undefined) {
                return Observable.throw("`remoteField` is undefined.");
              }
          
              const dataObject = {};
              if (
                this.remoteAdditionalFields &&
                this.remoteAdditionalFields !== undefined
              ) {
                const otherFields = this.remoteAdditionalFields.split(",");
                otherFields.forEach(field => {
                  const name = field.trim();
                  const otherControl = control.root.get(name);
                  if (otherControl) {
                    dataObject[name] = otherControl.value;
                  }
                });
              }
          
              // This is used to signal the streams to terminate.
              const changed$ = new Subject<any>();
              changed$.next(); // This will signal the previous stream (if any) to terminate.
          
              const debounceTime = 400;
          
              return new Observable((obs: any) => {
                control.valueChanges
                  .takeUntil(changed$)
                  .take(1)
                  .debounceTime(debounceTime)
                  .distinctUntilChanged()
                  .flatMap(term => {
                    dataObject[this.remoteField] = term;
                    return this.doRemoteValidation(dataObject);
                  })
                  .subscribe(
                    (result: IRemoteValidationResult) => {
                      if (result.result) {
                        obs.next(null);
                      } else {
                        obs.next({
                          remoteValidation: {
                            remoteValidationMessage: result.message
                          }
                        });
                      }
          
                      obs.complete();
                    },
                    error => {
                      obs.next(null);
                      obs.complete();
                    }
                  );
              });
            }
          
            private doRemoteValidation(data: any): Observable<IRemoteValidationResult> {
              const headers = new Headers({ "Content-Type": "application/json" }); // for ASP.NET MVC
              const options = new RequestOptions({ headers: headers });
          
              return this.http
                .post(this.remoteUrl, JSON.stringify(data), options)
                .map(this.extractData)
                .do(result => console.log("remoteValidation result: ", result))
                .catch(this.handleError);
            }
          
            private extractData(res: Response): IRemoteValidationResult {
              const body = <IRemoteValidationResult>res.json();
              return body || (<IRemoteValidationResult>{});
            }
          
            private handleError(error: Response): Observable<any> {
              console.error("observable error: ", error);
              return Observable.throw(error.statusText);
            }
          }
          
          export interface IRemoteValidationResult {
            result: boolean;
            message: string;
          }
          توضیحات تکمیلی

          ساختار Directive تهیه شده مانند همان مطلب «نوشتن اعتبارسنج‌های سفارشی برای فرم‌های مبتنی بر قالب‌ها در Angular» است، تنها با یک تفاوت:
          @Directive({
            selector:
              "[appRemoteValidator][formControlName],[appRemoteValidator][formControl],[appRemoteValidator][ngModel]",
            providers: [
              {
                provide: NG_ASYNC_VALIDATORS,
                useExisting: RemoteValidatorDirective,
                multi: true
              }
            ]
          })
          در اینجا بجای NG_VALIDATORS، از NG_ASYNC_VALIDATORS استفاده شده‌است.

          سپس ورودی‌های این دایرکتیو را مشاهده می‌کنید:
          export class RemoteValidatorDirective implements AsyncValidator {
            @Input("remote-url") remoteUrl: string;
            @Input("remote-field") remoteField: string;
            @Input("remote-additional-fields") remoteAdditionalFields: string;
          به این ترتیب زمانیکه appRemoteValidator به المانی اضافه می‌شود (نام selector این دایرکتیو)، سبب فعالسازی این اعتبارسنج می‌گردد.
          <input #username="ngModel" required maxlength="8" minlength="4" type="text"
                  appRemoteValidator [remote-url]="remoteUsernameValidationUrl" remote-field="FirstName"
                  remote-additional-fields="email,password" class="form-control" name="username"
                  [(ngModel)]="model.username">
          - در اینجا توسط ویژگی remote-url، آدرس اکشن متد سمت سرور دریافت می‌شود.
          - ویژگی remote-field مشخص می‌کند که اطلاعات المان جاری با چه کلیدی به سمت سرور ارسال شود.
          - ویژگی remote-additional-fields مشخص می‌کند که علاوه بر اطلاعات کنترل جاری، اطلاعات کدامیک از کنترل‌های دیگر را نیز می‌توان به سمت سرور ارسال کرد.

          یک نکته:
          ذکر "remote-field="FirstName به معنای انتساب مقدار رشته‌ای FirstName به خاصیت متناظر با ویژگی remote-field است.
          انتساب ویژه‌ی "remoteUsernameValidationUrl" به [remote-url]، به معنای انتساب مقدار متغیر remoteUsernameValidationUrl که در کامپوننت متناظر این قالب مقدار دهی می‌شود، به خاصیت متصل به ویژگی remote-url است.
          export class UserRegisterComponent implements OnInit {
             remoteUsernameValidationUrl = "api/Employee/CheckUser";
          بنابراین اگر remote-field را نیز می‌خواستیم به همین نحو تعریف کنیم، ذکر '' جهت مشخص سازی انتساب یک رشته، ضروری می‌بود؛ یعنی درج آن به صورت:
           [remote-field]="'FirstName'"


          ساختار مورد انتظار بازگشتی از سمت سرور

          در کدهای فوق، یک چنین ساختاری باید از سمت سرور بازگشت داده شود:
          export interface IRemoteValidationResult {
             result: boolean;
             message: string;
          }
          برای نمونه این ساختار را می‌توان توسط یک anonymous object ایجاد کرد و بازگشت داد:
          namespace AngularTemplateDrivenFormsLab.Controllers
          {
              [Route("api/[controller]")]
              public class EmployeeController : Controller
              {
                  [HttpPost("[action]")]
                  [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
                  public IActionResult CheckUser([FromBody] Employee model)
                  {
                      var remoteValidationResult = new { result = true, message = $"{model.FirstName} is fine!" };
                      if (model.FirstName?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false)
                      {
                          remoteValidationResult = new { result = false, message = "username:`Vahid` is already taken." };
                      }
          
                      return Json(remoteValidationResult);
                  }
              }
          }
          در اینجا برای مثال بررسی می‌شود که آیا FirstName ارسالی از سمت کاربر، معادل Vahid است یا خیر؟ اگر بله، result به false تنظیم شده و همچنین پیام خطایی نیز بازگشت داده می‌شود.
          همچنین اعتبارسنج سفارشی از راه دور فوق، پیام‌ها را تنها از طریق HttpPost ارسال می‌کند. علت اینجا است که در حالت POST، برخلاف حالت GET می‌توان اطلاعات بیشتری را بدون نگرانی از طول URL، ارسال کرد و همچنین کل درخواست، به علت وجود کاراکترهای غیرمجاز در URL (حالت GET، به درخواست یک URL از سرور تفسیر می‌شود)، برگشت نمی‌خورد.


          تکمیل کامپوننت فرم ثبت نام کاربران

          در ادامه تکمیل قالب user-register.component.html را مشاهده می‌کنید:
              <div class="form-group" [class.has-error]="username.invalid && username.touched">
                <label class="control-label">User Name</label>
                <input #username="ngModel" required maxlength="8" minlength="4" type="text"
                  appRemoteValidator [remote-url]="remoteUsernameValidationUrl" remote-field="FirstName"
                  remote-additional-fields="email,password" class="form-control" name="username"
                  [(ngModel)]="model.username">
                <div *ngIf="username.pending" class="alert alert-warning">
                  Checking server, Please wait ...
                </div>
                <div *ngIf="username.invalid && username.touched">
                  <div class="alert alert-danger"  *ngIf="username.errors.remoteValidation">
                    {{username.errors.remoteValidation.remoteValidationMessage}}
                  </div>
                </div>
              </div>
          در مورد ویژگی‌های appRemoteValidator پیشتر بحث شد. در اینجا تنها یک نکته‌ی جدید وجود دارد:
          زمانیکه یک async validator مشغول به کار است و هنوز پاسخی را دریافت نکرده‌است، خاصیت pending را به true تنظیم می‌کند. به این ترتیب می‌توان پیام اتصال به سرور را نمایش داد:


          همچنین چون در اینجا نحوه‌ی طراحی شکست اعتبارسنجی به صورت ذیل است:
          obs.next({
                          remoteValidation: {
                            remoteValidationMessage: result.message
                          }
                        });
          وجود کلید remoteValidation در مجموعه‌ی username.errors، بیانگر وجود خطای اعتبارسنجی از راه دور است و به این ترتیب می‌توان پیام دریافتی از سمت سرور را نمایش داد:



          مزایای استفاده از Observableها در حین طراحی async validators

          در کدهای فوق چنین مواردی را هم مشاهده می‌کنید:
              // This is used to signal the streams to terminate.
              const changed$ = new Subject<any>();
              changed$.next(); // This will signal the previous stream (if any) to terminate.
          
              const debounceTime = 400;
          
              return new Observable((obs: any) => {
                control.valueChanges
                  .takeUntil(changed$)
                  .take(1)
                  .debounceTime(debounceTime)
                  .distinctUntilChanged()
          در اینجا بجای کار مستقیم با control.value (روش متداول دسترسی به مقدار کنترل دریافتی در یک اعتبارسنج)، به رخ‌داد valueChanges آن متصل شده و سپس پس از 400 میلی‌ثانیه، جمع نهایی ورودی کاربر، در اختیار متد http.post برای ارسال به سمت سرور قرار می‌گیرد. به این ترتیب می‌توان تعداد رفت و برگشت‌های به سمت سرور را کاهش داد و به ازای هر یکبار فشرده شدن دکمه‌ای توسط کاربر، سبب بروز یکبار رفت و برگشت به سرور نشد.
          همچنین وجود و تعریف new Subject، دراینجا ضروری است و از نشتی حافظه و همچنین رفت و برگشت‌های اضافه‌ی دیگری به سمت سرور، جلوگیری می‌کند. این subject سبب می‌شود تا کلیه اعمال ناتمام پیشین، لغو شده (takeUntil) و تنها آخرین درخواست جدید رسیده‌ی پس از 400 میلی‌ثانیه، به سمت سرور ارسال شود.

          بنابراین همانطور که مشاهده می‌کنید، Observableها فراتر هستند از صرفا ارسال اطلاعات به سرور و بازگشت آن‌ها به سمت کلاینت (استفاده‌ی متداولی که از آن‌ها در برنامه‌های Angular وجود دارد).


          کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
          بازخوردهای دوره
          ارتباطات بلادرنگ و SignalR
          - نیازی به سرفصل جدا ندارد با توجه به خودکار بودن انتخاب لایه انتقال بر اساس توانایی سکوی کاری مورد استفاده (در حین کار با SignalR، وب سوکت فقط در ویندوز 8، IIS8 به همراه پروژه‌ای مبتنی بر دات نت 4 و نیم پشتیبانی می‌شود). سایر بحث‌ها و نکات یکی است و تفاوتی نمی‌کند. زمانیکه با Hub کار می‌کنید در لایه‌ای قرار دارید که این جزئیات از شما مخفی می‌شود و کار انتخاب خودکار است (تصویر abstraction level مطلب جاری).
          + دور‌ه‌ها در سایت قسمتی دارند جهت پرسش و پاسخ اختصاصی که می‌شود مشکلات و سؤالات مرتبط به دوره را در آنجا ارسال کرد با توضیح بیشتر.
          مطالب دوره‌ها
          تزریق وابستگی‌ها و سناریوهای بسیار متعدد موجود
          تعدادی از پرکاربردترین حالت‌های تزریق وابستگی‌ها را در دوره جاری بررسی کردیم. برای مثال چگونه می‌توان تزریق وابستگی‌های یک کنترلر ASP.NET MVC را خودکار کرد، یا در وب فرم‌ها وضعیت چگونه است. اما در حین حل مسایلی از این دست، به سناریوهای بسیار متعددی برخواهید خورد. برای مثال تزریق وابستگی‌های خودکار در یک کنترلر را آموختیم؛ در مورد فیلترها و Action Resultهای سفارشی چطور باید رفتار کرد؟ در WCF چطور؟ در هندلرهای وب فرم‌ها چطور؟ و بسیاری از حالات دیگر. البته تمام این موارد را توسط الگوی Service locator که شامل استفاده مستقیم از امکانات وهله سازی یک IoC Container، در کلاس مدنظر است، می‌توان حل کرد؛ اما باید تا حد امکان از این روش با توجه به اینکه خود IoC Container را تبدیل به یک وابستگی مدفون شده در کلاس‌های ما می‌کند، پرهیز نمود.
          اگر به دنبال کتابخانه‌ای هستید که بسیاری از این سناریوها را پیاده سازی کرده است، کتابخانه AutoFac پیشنهاد می‌شود. حتی اگر علاقمند به استفاده از آن نباشید، می‌توان از نحوه پیاده سازی‌های مختلف آن در مورد حالت‌های مختلف خودکار سازی تزریق وابستگی‌ها، ایده گرفت و سپس این کدها را با IoC Container مورد علاقه خود پیاده سازی کرد.

          صفحه خانگی AutoFac
          http://code.google.com/p/autofac
          http://autofac.org

          بسته نیوگت
          http://www.nuget.org/packages/Autofac

          محلی برای ایده گرفتن مثلا در مورد فیلترهای ASP.NET MVC
          و در مورد نحوه استفاده از آن‌ها، نیاز است آزمون‌های واحد این پروژه را بررسی کنید و یا مستندات پروژه را مطالعه کنید.
          همچنین بررسی لیست مستندات کلی آن نیز بسیار مفید است

          به صورت خلاصه، هرجایی در مورد تزریق وابستگی‌های خودکار جهت پرهیز از استفاده مستقیم از الگوی Service locator ایده‌ای نداشتید، سورس پروژه AutoFac را بررسی کنید.


          پ.ن.
          سایت bitbucket امکان import کامل مخازن کد Google code را نیز دارد (در صورتیکه دسترسی شما به گوگل کد محدود است).