مطالب
معرفی کتابخانه Postal برای ASP.NET MVC
Postal کتابخانه ای برای تولید و ارسال ایمیل توسط نما‌های ASP.NET MVC است. برای شروع این کتابخانه را به پروژه خود اضافه کنید. پنجره Package Manager Console  را باز کرده و فرمان زیر را اجرا کنید.
PM> Install-Package Postal

شروع به کار با Postal

نحوه استفاده از Postal در کنترلر‌های خود را در کد زیر مشاهده می‌کنید.
using Postal;

public class HomeController : Controller
{
  public ActionResult Index()
  {
      dynamic email = new Email("Example");
      email.To = "webninja@example.com";
      email.FunnyLink = DB.GetRandomLolcatLink();
      email.Send();
      return View();
  }
}
Postal نمای ایمیل را در مسیر Views\Emails\Example.cshtml جستجو می‌کند.
To: @ViewBag.To
From: lolcats@website.com
Subject: Important Message

Hello,
You wanted important web links right?
Check out this: @ViewBag.FunnyLink

<3


پیکربندی SMTP

Postal ایمیل‌ها را توسط SmtpClient ارسال می‌کند که در فریم ورک دات نت موجود است. تنظیمات SMTP را می‌توانید در فایل web.config خود پیکربندی کنید. برای اطلاعات بیشتر به MSDN Documentation مراجعه کنید.
<configuration>
  ...
  <system.net>
    <mailSettings>
      <smtp deliveryMethod="network">
        <network host="example.org" port="25" defaultCredentials="true"/>
      </smtp>
    </mailSettings>
  </system.net>
  ...
</configuration>

ایمیل‌های Strongly-typed

همه خوششان نمی‌آید از آبجکت‌های دینامیک استفاده کنند. علاوه بر آن آبجکت‌های دینامیک مشکلاتی هم دارند. مثلا قابلیت IntelliSense و یا Compile-time error را نخواهید داشت.
قدم اول - کلاسی تعریف کنید که از Email ارث بری می‌کند.
namespace App.Models
{
  public class ExampleEmail : Email
  {
    public string To { get; set; }
    public string Message { get; set; }
  }
}
قدم دوم - از این کلاس استفاده کنید!
public void Send()
{
  var email = new ExampleEmail
  {
    To = "hello@world.com",
    Message = "Strong typed message"
  };
  email.Send();
}
قدم سوم - نمایی ایجاد کنید که از مدل شما استفاده می‌کند. نام نما، بر اساس نام کلاس مدل انتخاب شده است. بنابراین مثلا ExampleEmail نمایی با نام Example.cshtml لازم دارد.
@model App.Models.ExampleEmail
To: @Model.To
From: postal@example.com
Subject: Example

Hello,
@Model.Message
Thanks!

آزمون‌های واحد (Unit Testing)

هنگام تست کردن کدهایی که با Postal کار می‌کنند، یکی از کارهایی که می‌خواهید انجام دهید حصول اطمینان از ارسال شدن ایمیل‌ها است. البته در بدنه تست‌ها نمی‌خواهیم هیچ ایمیلی ارسال شود.
Postal یک قرارداد بنام IEmailService و یک پیاده سازی پیش فرض از آن بنام EmailService ارائه می‌کند، که در واقع ایمیل‌ها را ارسال هم می‌کند. با در نظر گرفتن این پیش فرض که شما از یک IoC Container استفاده می‌کنید (مانند StructureMap, Ninject)، آن را طوری پیکربندی کنید تا یک نمونه از IEmailService به کنترلر‌ها تزریق کند. سپس از این سرویس برای ارسال آبجکت‌های ایمیل‌ها استفاده کنید (بجای فراخوانی متد ()Email.Send).
public class ExampleController : Controller 
{
    public ExampleController(IEmailService emailService)
    {
        this.emailService = emailService;
    }

    readonly IEmailService emailService;

    public ActionResult Index()
    {
        dynamic email = new Email("Example");
        // ...
        emailService.Send(email);
        return View();
    }
}
این کنترلر را با ساختن یک Mock از اینترفیس IEmailService تست کنید. یک مثال با استفاده از FakeItEasy را در زیر مشاهده می‌کنید.
[Test]
public void ItSendsEmail()
{
    var emailService = A.Fake<IEmailService>();
    var controller = new ExampleController(emailService);
    controller.Index();
    A.CallTo(() => emailService.Send(A<Email>._))
     .MustHaveHappened();
}

ایمیل‌های ساده و HTML

Postal ارسال ایمیل‌های ساده (plain text) و HTML را بسیار ساده می‌کند.
قدم اول - نمای اصلی را بسازید. این نما header‌ها را خواهد داشت و نما‌های مورد نیاز را هم رفرنس می‌کند. مسیر نما Views\Emails\Example.cshtml\~ است.
To: test@test.com
From: example@test.com
Subject: Fancy email
Views: Text, Html
قدوم دوم - نمای تکست را ایجاد کنید. به قوانین نامگذاری دقت کنید، Example.cshtml به Example.Text.cshtml تغییر یافته. مسیر فایل Views\Emails\Example.Text.cshtml است.
Content-Type: text/plain; charset=utf-8

Hello @ViewBag.PersonName,
This is a message
دقت داشته باشید که تنها یک Content-Type باید تعریف کنید.
قدم سوم - نمای HTML را ایجاد کنید (باز هم فقط با یک Content-Type). مسیر فایل Views\Emails\Example.Html.cshtml\~ است.
Content-Type: text/html; charset=utf-8

<html>
  <body>
    <p>Hello @ViewBag.PersonName,</p>
    <p>This is a message</p>
  </body>
</html>

ضمیمه ها

برای افزودن ضمائم خود به ایمیل ها، متد Attach را فراخوانی کنید.
dynamic email = new Email("Example");
email.Attach(new Attachment("c:\\attachment.txt"));
email.Send();


جاسازی تصاویر در ایمیل ها

Postal یک HTML Helper دارد که امکان جاسازی (embedding) تصاویر در ایمیل‌ها را فراهم می‌کند. دیگر نیازی نیست به یک URL خارجی اشاره کنید. 
ابتدا مطمئن شوید که فایل web.config شما فضای نام Postal را اضافه کرده است. این کار دسترسی به HTML Helper مذکور در نمای‌های ایمیل را ممکن می‌سازد.
<configuration>
  <system.web.webPages.razor>
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="Postal" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>
</configuration>
متد EmbedImage تصویر مورد نظر را در ایمیل شما جاسازی می‌کند و توسط یک تگ </img> آن را رفرنس می‌کند.
To: john@example.org
From: app@example.org
Subject: Image

@Html.EmbedImage("~/content/postal.jpg")
Postal سعی می‌کند تا نام فایل تصویر را، بر اساس مسیر تقریبی ریشه اپلیکیشن شما تعیین کند.


Postal بیرون از ASP.NET

Postal می‌تواند نماهای ایمیل‌ها را بیرون از فضای ASP.NET رندر کند. مثلا در یک اپلیکیشن کنسول یا یک سرویس ویندوز.
این امر توسط یک View Engine سفارشی میسر می‌شود. تنها نماهای Razor پشتیبانی می‌شوند. نمونه کدی را در زیر مشاهده می‌کنید.
using Postal;

class Program
{
    static void Main(string[] args)
    {
        // Get the path to the directory containing views
        var viewsPath = Path.GetFullPath(@"..\..\Views");

        var engines = new ViewEngineCollection();
        engines.Add(new FileSystemRazorViewEngine(viewsPath));

        var service = new EmailService(engines);

        dynamic email = new Email("Test");
        // Will look for Test.cshtml or Test.vbhtml in Views directory.
        email.Message = "Hello, non-asp.net world!";
        service.Send(email);
    }
}

محدودیت ها: نمی توانید برای نمای ایمیل هایتان از Layout‌ها استفاده کنید. همچنین در نماهای خود تنها از مدل‌ها (Models) می‌توانید استفاده کنید، و نه ViewBag.


Email Headers:  برای در بر داشتن نام، در آدرس ایمیل از فرمت زیر استفاده کنید.

To: John Smith <john@example.org>
Multiple Values: برخی از header‌ها می‌توانند چند مقدار داشته باشند. مثلا Bcc و CC. اینگونه مقادیر را می‌توانید به دو روش در نمای خود تعریف کنید:
جدا کردن مقادیر با کاما:
Bcc: john@smith.com, harry@green.com
Subject: Example

etc
و یا تکرار header:
Bcc: john@smith.com
Bcc: harry@green.com
Subject: Example

etc

ساختن ایمیل بدون ارسال آن

لازم نیست برای ارسال ایمیل هایتان به Postal تکیه کنید. در عوض می‌توانید یک آبجکت از نوع System.Net.Mail.MailMessage تولید کنید و به هر نحوی که می‌خواهید آن را پردازش کنید. مثلا شاید بخواهید بجای ارسال ایمیل ها، آنها را به یک صف پیام مثل MSMQ انتقال دهید یا بعدا توسط سرویس دیگری ارسال شوند. این آبجکت MailMessage تمامی Header ها، محتوای اصلی ایمیل و ضمائم را در بر خواهد گرفت.
کلاس EmailService در Postal متدی با نام CreateMailMessage فراهم می‌کند.
public class ExampleController : Controller 
{
    public ExampleController(IEmailService emailService)
    {
        this.emailService = emailService;
    }

    readonly IEmailService emailService;

    public ActionResult Index()
    {
        dynamic email = new Email("Example");
        // ...

        var message = emailService.CreateMailMessage(email);
        CustomProcessMailMessage(message);        

        return View();
    }
}

در این پست با امکانات اصلی کتابخانه Postal آشنا شدید و دیدید که به سادگی می‌توانید ایمیل‌های Razor بسازید. برای اطلاعات بیشتر لطفا به سایت پروژه Postal  مراجعه کنید.
مطالب
INPC استاندارد با بهره گیری از صفت CallerMemberName
یکی از Attribute‌های بسیار کاربردی که در سی شارپ 5 اضافه شد CallerMemberNameAttribute بود. این صفت به یک متد اجازه میدهد که از فراخواننده‌ی خود مطلع شود. این صفت را می‌توان بر روی یک پارامتر انتخابی که مقدار پیش‌فرضی دارد اعمال نمود.

استفاده از این صفت هم بسیار ساده است:

private void A ( [CallerMemberName] string callerName = "") 
{
  Console.WriteLine("Caller is " + callerName);
}

private static void B()
{
        // let's call A
        A();
}
در کد فوق، متد A به راحتی می‌تواند بفهمد چه کسی آن را فراخوانی کرده است. از جمله کاربردهای این صفت در ردیابی و خطایابی است.

ولی یک استفاده‌ی بسیار کاربردی از این صفت، در پیاده سازی رابط INotifyPropertyChanged می‌باشد.

معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را می‌نویسیم:

    public class PersonViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                this.name = value;
                OnPropertyChanged("Name");
            }
        }
    }

یعنی در Setter معمولا نام ویژگی ای را که تغییر کرده است، به متد OnPropertyChanged می‌فرستیم تا اطلاع رسانی‌های لازم انجام پذیرد. تا اینجای کار همه چیز خوب و آرام است. اما به محضی که کد شما کمی طولانی شود و شما به دلایلی نیاز به Refactor کردن کد و احیانا تغییر نام ویژگی‌ها را پیدا کنید، آن موقع مسائل جدیدی بروز پیدا می‌کند.

برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم می‌گیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که می‌خواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام می‌دهید. همانند کد زیر:

private string firstName;
public string FirstName
{
            get { return firstName; }
            set
            {
                this.firstName = value;
                OnPropertyChanged("Name");
            }
}

برنامه را کامپایل کرده و در کمال تعجب می‌بینید که بخشی از برنامه درست رفتار نمی‌کند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد می‌شود به درستی بروزرسانی نمی‌شوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینه‌ی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کرده‌ایم. پس جای تعجبی ندارد که در هر جای کد که رشته‌ای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش می‌کنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.

حال احتمال بروز این خطا را در ViewModel هایی با ده‌ها ویژگی و ترکیب‌های مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.

و اما راه حل چیست؟ به کارگیری صفت CallerMemberName

بهتر است که یک کلاس انتزاعی برای تمام ViewModel‌های خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VM‌های ما از آن مشتق شوند:

public abstract class ViewModelBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            OnPropertyChangedExplicit(propertyName);
        }

        protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
        {
            var memberExpression = (MemberExpression)projection.Body;
            OnPropertyChangedExplicit(memberExpression.Member.Name);
        }

        void OnPropertyChangedExplicit(string propertyName)
        {
            this.CheckPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;

            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #region Check property name

        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void CheckPropertyName(string propertyName)
        {
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                throw new Exception(String.Format("Could not find property \"{0}\"", propertyName));
        }

        #endregion // Check property name
}

در این کلاس، ما پارامتر propertyName را از متد OnPropertyChanged، توسط صفت CallerMemberName حاشیه نویسی کرده‌ایم. این کار باعث می‌شود در Setter‌های ویژگی‌ها، به راحتی بدون نوشتن نام ویژگی، عملیات اطلاع رسانی تغییرات را انجام دهیم. بدین صورت که کافیست متد OnPropertyChanged بدون هیچ آرگومانی در Setter فراخوانی شود و صفت CallerMemberName به صورت اتوماتیک نام ویژگی ای که فراخوانی از درون آن انجام شده است را درون پارامتر propertyName قرار می‌دهد.

پس کلاس PersonViewModel را به صورت زیر می‌توانیم اصلاح و تکمیل کنیم:

public class PersonViewModel : ViewModelBase
{
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                this.firstName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                this.lastName = value;

                OnPropertyChanged();
                OnPropertyChanged(() => this.FullName);
            }
        }

        public string FullName
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
}
همانطور که می‌بینید متد OnPropertyChanged بدون آرگومان فراخوانی میشود. اکنون اگر شما اقدام به Refactor کردن کد خود بکنید دیگر نگرانی از بابت تغییر نکردن رشته‌ها و کامنت‌ها نخواهید داشت و مطمئن هستید، نام ویژگی هر چیزی که باشد، به صورت خودکار به متد ارسال خواهد شد.

کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه می‌دهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق می‌بینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیله‌ی یک Func به راحتی بیان می‌کنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.

برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.

ارجاع:
Raise INPC witout string name
مطالب
اثر وجود سشن بر پردازش موازی در ASP.NET
در حین جستجوی مطلبی در فوروم‌های ASP.NET، مطلبی رو از یکی از اعضای تیم ASP.NET دیدم که خلاصه‌اش به این شرح است:

اگر چندین درخواست با یک Session ID به موتور پردازشی ASP.NET وارد شوند و حداقل یکی از آن‌ها به صورت سشنی خواندنی+نوشتنی علامت گذاری شده باشد، اینگونه درخواست‌ها serialized می‌شود. به این معنا که این نوع درخواست‌ها در صف قرار داده شده و به ترتیب رسیدن، اجرا می‌شوند. این کار هم از این جهت لازم است که چندین درخواست یا به عبارتی چندین ترد همزمان، سبب انجام تغییرات ناخواسته‌ای در مقادیر سشن مورد استفاده نشوند.
و اگر چندین درخواست با یک Session ID به موتور پردازشی ASP.NET وارد شوند و تمام آن‌ها سشن «فقط خواندنی» باشند یا در آن‌ها سشن «غیرفعال» شده باشد، این درخواست‌ها به صورت موازی پردازش خواهند شد.

بنابراین اگر در برنامه خود از سشن استفاده نمی‌کنید، سطر زیر را به وب کانفیگ برنامه اضافه نمائید تا حداکثر استفاده را از پردازش موازی خودکار کرده باشید:

<httpModules>
   <remove name="Session"/>
</httpModules>

<sessionState mode="Off" />

و یا در ASP.NET MVC می‌توان کنترل ریزتری را به نحو زیر نیز اعمال کرد:
[SessionState(SessionStateBehavior.Disabled)]
public class AjaxTestController : Controller
{  
   //...
}
در اینجا تنها حالت سشن مرتبط با اکشن متدی (و یا تمام اکشن متدهای یک کنترلر) که برای درخواست‌های Ajax طراحی شده، خاموش گردیده است تا درخواست‌های غیرهمزمان Ajaxایی بتوانند حداکثر استفاده را از پردازش موازی خودکار کنند.
نکته دیگر اینکه اگر نیاز به استفاده از سشن در یک اکشن متد وجود داشته باشد می‌توان از فیلتر سشن‌های فقط خواندنی استفاده کرد تا باز هم به همان پردازش موازی پشت صحنه برسیم:
[SessionState(SessionStateBehavior.ReadOnly)]
مطالب
آشنایی با CLR: قسمت ششم
در مقاله قبلی مبحث کامپایلر JIT را آغاز کردیم. در این قسمت قصد داریم مبحث کارآیی CLR و مباحث دیباگینگ را پیش بکشیم.
از آنجا که یک کد مدیریت نشده، مبحث کارهای JIT را ندارد، ولی CLR مجبور است وقتی را برای آن بگذارد، به نظر می‌رسد ما با یک نقص کوچک در کارآیی روبرو هستیم. گفتیم که جیت کدها را در حافظه‌ی پویا ذخیره می‌کند. به همین خاطر با terminate شدن یا خاتمه دادن به برنامه، این کدها از بین می‌روند یا اینکه اگر دو نمونه از برنامه را اجرا کنیم، هر کدام جداگانه کد را تولید می‌کنند و هر کدام برای خودشان حافظه‌ای بر خواهند داشت و اگر مقایسه‌ای با کدهای مدیریت نشده داشته باشید، در مورد مصرف حافظه یک مشکل ایجاد می‌کند. همچنین JIT در حین تبدیل به کدهای بومی یک بهینه سازی روی کد هم انجام میدهد که این بهینه سازی وقتی را به خود اختصاص می‌دهد ولی همین بهینه سازی کد موجب کارآیی بهتر برنامه می‌گردد.
در زبان سی شارپ دو سوئیچ وجود دارند که بر بهینه سازی کد تاثیر گذار هستند؛ سوئیچ‌های debug و optimize. در جدول زیر تاثیر هر یک از سوئیچ‌ها را بر کیفیت کد IL و JIT در تبدیل به کد بومی را نشان میدهد.

موقعیکه از دستور -optimize استفاده می‌شود، کد IL تولید شده شامل تعداد زیادی از دستورات بدون دستورالعمل No Operation یا به اختصار NOP و پرش‌های شاخه‌ای به خط کد بعدی می‌باشد. این دستور العمل‌ها ما را قادر میسازند تا ویژگی edit & Continue را برای دیباگ کردن و یک سری دستورالعمل‌ها را برای کدنویسی راحت‌تر برای دیباگ کردن و ایجاد break point‌ها داشته باشیم.

موقعی که کد IL بهینه شده تولید شود، این خصوصیات اضافه حذف خواهند شد و دنبال کردن خط به خط کد، کار سختی می‌شود. ولی در عوض فایل نهایی exe یا dll، کوچکتر خواهد شد. بهینه سازی IL توسط JIT حذف خواهد شد و برای کسانی که دوست دارند کدهای IL را تحلیل و آنالیز کنند، خواندنش ساده‌تر و آسان‌تر خواهد بود.

نکته‌ی بعدی اینکه موقعیکه شما از سوئیچ (/debug(+/full/pdbonly استفاده می‌کنید، یک فایل PDB  یا Program Database ایجاد می‌شود. این فایل به دیباگرها کمک می‌کند تا متغیرهای محلی را شناسایی و به کدهای IL متصل شوند. کلمه‌ی full بدین معنی است که JIT می‌تواند دستورات بومی را ردیابی کند تا مبداء آن کد را پیدا کند. سبب می‌شود که ویژوال استودیو به یک دیباگر متصل شده تا در حین اجرای پروسه، آن را دیباگ کند. در صورتی که این سوئیچ را استفاده نکنید، به طور پیش فرض پروسه اجرا و مصرف حافظه کمتر می‌شود. اگر شما پروسه‌ای را اجرا کنید که دیباگر به آن متصل شود، به طور اجباری JIT مجبور به انجام عملیات ردیابی خواهد شد؛ مگر اینکه گزینه‌ی suppress jit  optimization on module load را غیرفعال کرده باشید.
موقعیکه در ویژوال استودیو دو حالت دیباگ و ریلیز را انتخاب می‌کنید، در واقع تنظیمات زیر را اجرا می‌کنید:

//debug

/optimize­ 
/debug:full

//=======================

//Release

/optimize+
/debug:pdbonly
احتمالا موارد بالا به شما می‌گویند که یک سیستم مبتنی بر CLR مشکلات زیادی دارد که یکی از آن‌ها، زمان‌بر بودن انجام عملیات فرآیند پردازش است و دیگری مصرف زیاد حافظه و عدم اشترک حافظه که در مورد کامپایل جیت به آن اشاره کردیم. ولی در بند بعدی قصد داریم نظرتان را عوض کنم.

اگر خیلی شک دارید که واقعا یک برنامه‌ی CLR کارآیی یک برنامه را پایین می‌آورد، بهتر هست به بررسی کارآیی چند برنامه غیر آزمایشی noTrial که حتی خود مایکروسافت آن برنامه‌ها را ایجاد کرده است بپردازید و آن‌ها را با یک برنامه‌ی unmanaged مقایسه کنید. قطعا باعث تعجب شما خواهد شد. این نکته دلایل زیادی دارد که در زیر تعدادی از آن‌ها را بررسی می‌کنیم.
اینکه CLR در محیط اجرا قصد کمپایل دارد، باعث آشنایی کامپایلر با محیط اجرا می‌گردد. از این رو تصمیماتی را که می‌گیرد، می‌تواند به کارآیی یک برنامه کمک کند. در صورتیکه یک برنامه‌ی unmanaged که قبلا کمپایل شده و با محیط‌های متفاوتی که روی آن‌ها اجرا میشود، هیچ آشنایی ندارد و نمیتواند از آن محیط‌ها حداکثر بهره‌وری لازم را به عمل آورد.
برای آشنایی با این ویژگی‌ها توجه شما را به نکات ذیل جلب می‌کنم:

یک.  JIT می‌تواند با نوع پردازنده آشنا شود که آیا این پردازنده از نسل پنتیوم 4 است یا نسل Core i. به همین علت می‌تواند از این مزیت استفاده کرده و دستورات اختصاصی آن‌ها را به کار گیرد، تا برنامه با performance بالاتری اجرا گردد. در صورتی که unmanaged باید حتما دستورات را در پایین‌ترین سطح ممکن و عمومی اجرا کند؛ در صورتیکه شاید یک دستور اختصاصی در یک سی پی یو خاص، در یک عملیات موجب 4 برابر، اجرای سریعتر شود.

دو.  JIT میتواند بررسی هایی را که برابر false هستند، تشخیص دهد. برای فهم بهتر، کد زیر را در نظر بگیرید:
if (numberOfCPUs > 1) {
...
}

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

سه. مورد بعدی که هنوز پیاده سازی نشده، ولی احتمال اجرای آن در آینده است، این است که یک کد می‌تواند جهت تصحیح بعضی موارد چون مسائل مربوط به دیباگ کردن و مرتب سازی‌های مجدد، عمل کامپایل را مجددا برای یک کد اعمال نماید.
دلایل بالا تنها قسمت کوچکی است که به ما اثبات می‌کند که چرا CLR می‌تواند کارآیی بهتری را نسبت به زبان‌های unmanaged امروزی داشته باشد. همچنین قول‌هایی از سازندگان برای بهبود کیفیت هر چه بیشتر این سیستم‌ها به گوش می‌رسد.

کارآیی بالاتر
اگر برنامه‌ای توسط شما بررسی شد و دیدید که نتایج مورد نیاز در مورد performance را نشان نمی‌دهد، می‌توانید از ابزار کمکی که مایکروسافت در بسته‌های فریمورک دات نت قرار داده است استفاده کنید. نام این ابزار Ngen.exe است و وظیفه‌ی آن این است که وقتی برنامه بر روی یک سیستم برای اولین مرتبه اجرا می‌گردد، کد همه‌ی اسمبلی‌ها را تبدیل کرده و آن‌ها روی دیسک ذخیره می‌کند. بدین ترتیب در دفعات بعدی اجرا، JIT بررسی می‌کند که آیا کد کامپایل شده‌ی اسمبلی از قبل موجود است یا خیر. در صورت وجود، عملیات کامپایل به کد بومی لغو شده و از کد ذخیره شده استفاده خواهد کرد.
نکته‌ای که باید در حین استفاده از این ابزار به آن دقت کنید این است که کد در محیط‌های واقعی اجرا چندان بهینه نیست. بعدا در مورد این ابزار به تفصیل صحبت می‌کنیم.

system.runtime.profileoptimization
کلاس بالا سبب می‌شود که CLR در یک فایل ثبت کند که چه متدهایی در حین اجرای برنامه کمپایل شوند تا در آینده در حین آغاز اجرای برنامه کامپایلر JIT بتواند همزمان این متدها را در ترد دیگری کامپایل کند. اگر برنامه‌ی شما روی یک پردازنده‌ی چند هسته‌ای اجرا می‌شود، در نتیجه اجرای سریعتری خواهید داشت. به این دلیل که چندین متد به طور همزمان در حال کمپایل شدن هستند و همزمان با آماده سازی برنامه برای اجرا اتفاق می‌افتد؛ به جای اینکه عمل کمپایل همزمان با تعامل کاربر با برنامه باشد.

مطالب
پشتیبانی توکار از GDPR در ASP.NET Core 2.1
دیروز (25 ماه May سال 2018) اولین روز فعالسازی GDPR یا General Data Protection Regulation بود و به همین خاطر است که اگر به سرویس‌های مهم اینترنتی دقت کرده باشید، پر شده‌است از پیام‌هایی مانند «ما از کوکی استفاده می‌کنیم»، «ما اطلاعات شما را به این صورت ذخیره می‌کنیم» و امثال آن. همچنین تعداد زیادی از سرویس‌های اینترنتی نیز به کاربران خود پیام‌هایی را جهت تائید قوانین جدید رعایت حریم خصوصی آن‌ها ارسال کرده‌اند. برای مثال اگر این قوانین جدید را تائید نکنید، از دریافت بسیاری از خبرنامه‌ها محروم خواهید شد. این مورد نیز از بخش‌نامه‌ی اتحادیه‌ی اروپا نشات می‌گیرد که از روز جمعه ۲۵ می‌(۴ خرداد) تمامی شرکت‌ها، افراد، وب‌سایت‌ها و ارائه‌دهندگان خدمات آنلاین، موظف به رعایت آن هستند. موضوع بخش‌نامه قبل از هرچیزی، حفاظت از اطلاعات خصوصی کاربران است. نهادها و شرکت‌ها و وب‌سایت‌هایی که تا ۲۵ می‌۲۰۱۸ زمینه اجرای این بخش‌نامه را فراهم نکرده باشند، در خطر جریمه‌های سنگین هستند. بخش‌نامه جدید حریم خصوصی اطلاعات، تعیین می‌کند که چه میزان اطلاعاتی درباره‌ی هرکسی می‌تواند جمع‌آوری و بررسی شود، مورد پردازش قرار گیرد و البته تبدیل به پول شود. این بخش‌نامه حق تک‌تک کاربران، بر اطلاعات‌شان را تقویت می‌کند. کاربران حالا حق بیشتری بر اطلاعات‌شان، برای «پاک کردن» آن‌ها و «پس‌گرفتن» آن‌ها دارند. البته آن‌چه که احتمالا برای همه قابل رؤیت خواهد بود توضیحات مربوط به حفاظت از اطلاعات در وبسایت‌ها است. این توضیحات باید جزئی‌تر و دقیق‌تر و برای مخاطبان قابل فهم‌تر باشند و این به این معنا است که این توضیحات به‌مراتب طولانی‌تر خواهند شد.
در این بین اگر به قالب پیش‌فرض پروژه‌های MVC تولید شده‌ی توسط ASP.NET Core 2.1 نیز دقت کنید، پشتیبانی توکار از پیشنیازهای GDPR در آن لحاظ شده‌است؛ چه از لحاظ گوشزد کردن شرایط حریم خصوصی و پذیرش آن و چه از لحاظ «پاک کردن» و «پس گرفتن» اطلاعات شخصی.


قالب و کوکی پذیرش شرایط حریم خصوصی سایت (Cookie Consent)


اگر قالب پیش‌فرض یک پروژه‌ی ASP.NET Core 2.1 را اجرا کنید، تصویر فوق را که در آن نوار پذیرش شرایط حریم خصوصی سایت در بالای صفحه درج شده‌است، مشاهده خواهید کرد.
قالب جدید نوار پذیرش شرایط حریم خصوصی در مسیر Views\Shared\_CookieConsentPartial.cshtml واقع شده‌است و در فایل layout برنامه توسط tag helper جدید Partial، رندر و نمایش داده می‌شود:
<partial name="_CookieConsentPartial" />
در ابتدای این partial view، یک چنین کدهایی درج شده‌اند:
@using Microsoft.AspNetCore.Http.Features
@{
  var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
  var showBanner = !consentFeature?.CanTrack ?? false;
  var cookieString = consentFeature?.CreateConsentCookie();
}
بنابراین پذیرش شخص را در یک کوکی درج می‌کند و در دفعات بعدی بازدید او بر اساس این کوکی است که در مورد نمایش یا عدم نمایش این نوار پذیرش شرایط، تصمیم گیری خواهد شد. این کوکی نیز که تحت عنوان میان‌افزار CookiePolicy در سیستم مدیریت و پردازش می‌شود، به صورت زیر در فایل آغازین برنامه مدیریت می‌گردد:
الف) تنظیم نیاز به دریافت پذیرش
public void ConfigureServices(IServiceCollection services)
{
   services.Configure<CookiePolicyOptions>(options =>
   {
     // This lambda determines whether user consent for non-essential cookies is needed for a given request.
     options.CheckConsentNeeded = context => true;
     options.MinimumSameSitePolicy = SameSiteMode.None;
   });
ب) فعالسازی میان‌افزار مدیریت کوکی پذیرش شرایط حریم خصوصی
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   // ...
   app.UseCookiePolicy();


دادن حق فراموش شدن به کاربران

علاوه بر Cookie Consent فوق که در یک قالب ابتدایی MVC نیز درج شده‌است، در قالب پروژه‌های ASP.NET Core Identity، دو گزینه‌ی جدید دریافت اطلاعات شخصی و همچنین حذف اکانت (دادن حق فراموشی به کاربران) نیز پیش‌بینی شده‌است: PersonalData.cshtml


البته این صفحه جزو بسته‌ی جدید Microsoft.AspNetCore.Identity.UI است که به همراه ASP.NET Core 2.1 ارائه می‌شود:
 dotnet add package Microsoft.AspNetCore.Identity.UI --version 2.1.0-rc1-final
در این بسته تمام کدها و صفحات مخصوص Identity به داخل یک Class library جدید منتقل شده‌اند و دیگر جزو قالب پروژه‌ی «dotnet new mvc --auth Individual» یا همان تنظیم اعتبارسنجی به individual user accounts نیستند و باید به صورت جداگانه دریافت و تنظیم شوند (اختیاری است).
نظرات مطالب
EF Code First #2
با سلام و تشکر

سوال اول:
این قسمت آخری را که فرمودید:
"در EF Code first به صورت پیش فرض همه چیز بر مبنای کاربری با دسترسی
مدیریتی یا dbo schema در اس کیوال سرور تنظیم شده است. اما اگر کاربر خاصی
برای کار با دیتابیس تعریف گردد که در هاست‌های اشتراکی بسیار مرسوم است،
دیگر از دسترسی مدیریتی dbo خبری نخواهد بود."

من متوجه نشدم! ما در هاستهای اشتراکی مثلا از طریق پنل پلسک یک بانک به همراه یک کاربر به نام فرضی user1 ایجاد می کنم و در کانشکن استرینگ هم با همین نام کاربری متصل می شویم. حال منظور شما از کاربر خاص یعنی چه کسی؟
این scheme که نام آنرا someUser گذاشتید، مربوط به چه کسی است و از کجا آمده است؟


سوال دوم:
آیا در مورد بانک Membership پیش فرض مایکروسافت و تلفیق آن با بانک اصلی برنامه در EF راه کاری اندیشیده شده؟
بنده هیچ وقت از این امکان به جهت دو دسته شدن جداول و ساختار بانکم استفاده نکردم ولی با توجه به یکپارچه شدن آن با ASP.NET MVC کم کم دارم متقاعد می شوم که به جای منطق Membership خودم از این امکان استفاده کنم، نظر شما در مورد منطق Membership برنامه ای که با EF و MVC نوشته می شود چیست؟
مطالب
مقدمه ای بر CQRS و Event Sourcing
به صورت عام، functionality اکثر پروژه‌های نرم افزاری تجاری خلاصه میشود به مخفف معروفی به نام CRUD، که object‌ها را میسازیم، آن‌ها را میخوانیم و تغییر میدهیم.
اپلیکیشن‌های طراحی شده بدین صورت، قابلیت خوانایی بالایی خواهند داشت و دیاگرام طراحی آنها چیزی شبیه به تصویر زیر میباشد

در واقع ما یک سیستمی داریم که شامل مدلی است از دیتا‌های ما و از این مدل برای کوئری گرفتن از دیتابیس استفاده میشود، که البته برای بیشتر پروژه‌های نرم افزاری، معماری درست و ترجیح داده شده‌ای هم میباشد.

زمانیکه نیاز‌های پروژه روز به روز افزورده و پیچیده‌تر میشود، مدل CRUD بصورت پیوسته از ارزشش کاسته میشود و از آن سادگی اولیه‌ی در درک و خوانایی آن دور خواهد شد.

ذات CQRS بر آن است که شما مدل‌های مختلفی را برای خواندن و نوشتن دیتا داشته باشید. الگوی آن چیزی شبیه به تصویر زیر است

چیزی که در این روش مشهود است این میباشد که برنامه نویسان باید قسمت‌های Command و Query را به صورت جداگانه طراحی نمایند.
CQRS این قابلیت را به شما میدهد که interface و Datastore و حتی بطور کامل Technology مجزایی در قسمت‌های CQ داشته باشید.

Event Sourcing
قسمت دوم و متمایز، معماری Event Sourcing یا ES میباشد که بصورت کوتاه، ES یک روش متفاوت برای Data storage میباشد.
اکثرا ما از Datastore‌هایی که مدلی از دیتا را انعکاس می‌دهند استفاده میکنیم. به مثال ساده‌ی زیر توجه کنید

ES از برنامه نویسان میخواهد که مدل سنتی CRUD را فراموش کرده و بجای آن تغییراتی را که روی دیتا صورت گرفته، نیز درج نمایند. اینکار به وسیله‌ی یک دیتابیس Append-only انجام میشود که به نام Event Store شناخته میشود.

در این معماری ما همه‌ی تغییرات روی دیتا را به صورت Serialize Event ذخیره میکنیم که میتواند دوباره در هر زمانی اجرا شده و current state هر objectی را در اختیار بگذارد.

این روش به ما کمک بزرگی میکند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن میتوان به غیر از فوایدی که دارد، به عنوان یک Logger نیز استفاده نمود. به دلیل اینکه جزء به جزء تغییرات بر روی state سیستم، در آن ثبت شده است. از آنجاییکه دیتا بصورت serialize ذخیره میشود، بارگزاری آن نیز با سرعت بالایی انجام خواهد شد.

پس بصورت خلاصه در معماری Command Query Responsibility Segregation ابتدا باید به این موضوع توجه داشت که قسمت‌های Read و Write نرم افزار به صورت مجزایی طراحی میشوند و Event Sourcing شامل تغییراتی که روی data انجام شده است، میباشد و به‌صورت Serialize شده ذخیره میشود. ما تنها به یک دیتابیس و یک جدول برای نمایش event store نیازمندیم (بستگی به نیازتان میتوان تعداد آن را نیز بیشتر نمود و همچنین حتما لزومی ندارد که از دیتابیس‌های رابطه‌ای استفاده شود؛ بصورت مثال پیاده سازی این قسمت را میتوان با استفاده از Redis که دیتابیسی غیر رابطه‌ای و باسرعت میباشد استفاده نمود).
برای شروع کار (نه پیاده سازی کامل) باید با قسمت‌های مختلف طراحی در این معماری آشنا شویم:
Domain Object
نکته: SimpleCqrs فریم ورکی برای پیاده سازی معماری CQRS , ES میباشد که برای ساده‌تر شدن کار، از آن استفاده شده است (شما حتی میتوانید پیاده سازی خود را داشته باشید)
مدل Movie از کلاسی به نام AggregateRoot ارث بری کرده‌است که توسط SimpleCQRS پیاده سازی شده‌است و یک guid key در آن تعبیه شده است (Aggregate root از مباحث Domain Driven برگرفته شده است و آشنایی با آن کمک شایانی به درک عمیق‌تر روی این مباحث مینماید).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie() { }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        //پیاده سازی خواهد شد
    }
}
توجه: SimpleCQRS فقط پیاده سازی guid برای کلید مربوط به هر مدل را پیاده سازی نموده است؛ بنابراین کلید مدل نمیتواند integer باشد.

Commands
command دستوراتی است که توسط end user فراخوانی میشود که باعث تغییرات خواهد شد. وقتی اپلیکیشن یک command را دریافت مینماید، command handler به پردازش آن برای فهمیدن خواسته کاربر میپردازد و پس از آن event مربوطه را برای اجرای آن وظیفه‌ی خاص صدا میزند.
همه‌ی command‌ها تغییراتی بر روی state جاری خواهند داشت. در نتیجه دیتا‌های ذخیره شده درون دیتابیس تغییرات خواهند کرد. هر commandی که تغییری بر روی State سیستم نداشته باشد، یک دستور غلط محسوب شده و باید در سمت query‌ها آن را پیاده سازی نمود.
در نتیجه Commnad‌ها دستوراتی هستند که از طرف کاربر برای تغییرات بر روی دیتا‌های ذخیره شده، ارسال میشوند.
فرض کنید Domain Objectی برای Movie تعریف کرده‌ایم و میخواهیم دستور اضافه کردن فیلم را پیاده سازی نماییم
public class CreateMovieCommand : ICommand  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }
    public CreateMovieCommand(string title, DateTime releaseDate, int runningTime)
    {
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
توجه: ICommand از طریق SimpleCQRS اضافه شده‌است.

Command Handler
بعد از اینکه Command مورد نیاز نوشته شد، حال احتیاج به پیاده سازی CommandHandler مربوطه که دستور متناظر را پردازش میکند، داریم.
public class CreateMovieCommandHandler : CommandHandler<CreateMovieCommand>  
{
    protected IDomainRepository _repository;

    public CreateMovieCommandHandler(IDomainRepository repository)
    {
        _repository = repository;
    }

    public override void Handle(CreateMovieCommand command)  
    {
        var movie = new Domain.Movie(Guid.NewGuid(), command.Title, 
    command.ReleaseDate, command.RunningTimeMinutes);

        _repository.Save(movie);
    }
}
Command Handler باید از کلاس جنریک <CommandHandler<T ارث بری نماید و T باید از نوع Command در نظر گرفته شود و همچنین IDomainRepository اینترفیسی است که توسط SimpleCQRS تعریف شده‌است و ما احتیاجی به پیاده سازی آن نداریم (در قسمت‌های بعدی پیکربندی آن را انجام میدهیم).
برای رسیدگی کردن به دستور مربوطه احتیاج به override کردن متد Handle میباشد.
کار اساسی توسط متد Save انجام میشود که همه‌ی event‌های pending شده توسط Domain Object را گرفته و آنها را به Event Store میفرستد.

Events
event‌ها تغییراتی هستند بر روی State جاری سیستم که توسط کاربر به وسیله‌ی Commandها فراخوانی میشوند.
رویداد‌ها serialize میشوند و درون Event Store ذخیره میشوند؛ بنابراین میتوان فراخوانی آنها را در هر لحظه انجام داد.
هر تعداد Event میتواند توسط یک دستور raise شود.
ساخت یک Event:
قبلا دستوری را برای ساخت یک movie نوشتیم و حال احتیاج به event مربوطه را داریم:
public class MovieCreatedEvent : DomainEvent  
{
    public Guid MovieId
    {
        get { return AggregateRootId; }
        set { AggregateRootId = value;}
    }

    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public MovieCreatedEvent(Guid movieId, string title, DateTime releaseDate, int runningTime)
    {
        MovieId = movieId;
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
فراموش نکنید که این کلاس آبجکتی خواهد بود که Serialize شده و در دیتابیس ذخیره خواهد شد. باید همه‌ی پراپرتی‌های لازم که با استفاده از این Event ممکن است تغییر کنند را شامل شود (بدیهی است که این پراپرتی‌ها از Domain Object گرفته میشود).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        Apply(new MovieCreatedEvent(Guid.NewGuid(), title, releaseDate, runningTimeMinutes));
    }
}
به Aggregate فوق که در اوایل بحث صحبت شده‌است دقت کنید. حال متد Apply باعث میشود که event مربوطه درون بخش لوکال aggregate root ذخیره شود. بنابراین بعدا میتواند به صورت فیزیکی درون Event Store ذخیره شود.

Event Handler
هر Event Handler  میتواند تعداد زیادی از IHandleDomainEvents ‌ها را پیاده سازی نماید. حال متد Handle این اینترفیس را پیاده سازی نمودیم. 
public class MovieEventHandler : IHandleDomainEvents<MovieCreatedEvent>  
{
    public void Handle(MovieCreatedEvent createdEvent)
    {
        using (MoviesContext entities = new MoviesContext())
        {
            entities.Movies.Add(new Movie()
            {
                Id = createdEvent.AggregateRootId,
                Title = createdEvent.Title,
                ReleaseDate = createdEvent.ReleaseDate,
                RunningTimeMinutes = createdEvent.RunningTimeMinutes
            });

            entities.SaveChanges();
        }
    }
}
مثلا در این قسمت با استفاده از ORM، شیء مورد نظر به صورت فیزیکی درون دیتابیس ذخیره میشود.
در قسمت آخر نیازمندیم که تغییرات زیر را به Movie اضافه نماییم.
درون Doamin Objectی که قبلا تعریف کرده بودیم متدی را به صورت زیر پیاده سازی مینماییم
protected void OnMovieCreated(MovieCreatedEvent domainEvent)
        {
            Id = domainEvent.AggregateRootId;
            Title = domainEvent.Title;
            ReleaseDate = domainEvent.ReleaseDate;
            RunningTimeMinutes = domainEvent.RunningTimeMinutes;
        }
باعث میشود پس از فراخوانی شدن Event، تغییرات صورت گرفته‌ی بر state سیستم، بر روی Domain Object اعمال شود و آن را بروزرسانی نماید. این متد دقیقا بصورت اتوماتیک وقتی که event مربوطه raise میشود، فراخوانی میشود.
پس از ترکیب CQRS و ES معماری اولیه‌ی سیستم چیزی شبیه به دیاگرام زیر خواهد بود (بسته به سناریوهای خاص میتواند سفارشی سازی شود)

خلاصه:
کاربر دستوری را از طریق برنامه به سیستم ارسال مینماید.
command مربوطه دریافت میشود و به روی Command Bus قرار داده میشود.
Command Handler وظیفه‌ی تفسیر کردن Command مربوطه را به عهده میگیرد و به وسیله‌ی Domain object آن event مورد نظر فراخوانی خواهد شد و باعث میشود domain object بروزرسانی گردد.
Event همان objectی است که باید به صورت serialize شده درون append only database ذخیره شود.
Event handler رویداد مربوطه را گرفته و بصورت فیزیکی مقادیر مورد نظر را در دیتابیس ذخیره مینماید.

Query
از آنجاییکه قسمت Read، در سیستم به صورت CQRS طراحی میشود، به راحتی میتوان query‌ها را optimize کرده و به صورت مثال به جای استفاده از ORM‌های معمول بطور مستقیم Stored Procedure فراخوانی کرده، تا جای ممکن کیفیت query‌ها بهترین حالت ممکن باشند. در حالیکه در مدل CRUD بهینه کردن بخش read بسیار پیچیده و بعضا غیر ممکن میباشد.

مزایای استفاده از این مدل
Distributed Systems Capabilities
یکی از مهمترین مزیت‌های این مدل تسهیم گسترش پذیری سیستم بر روی ماشین‌های فیزیکی مختلف از طریق messaging pattern میباشد.
High Availability
از آنجایی که سیستم توزیع پذیر طراحی شده‌است، هر قسمت از آن میتواند بدون توجه به fail شدن قسمت‌های دیگر به کار خود ادامه دهد.
Reduce Complexity
در domain‌های پیچیده طراحی و پیاده سازی objectهایی که مسئول دو قسمت read و write هستند، میتواند کار را بیش از حد پیچیده کرده و در این صورت چون business logic و read logic در هم ترکیب میشوند، مدیریت کردن موارد multiple user, shared data, performance, transactions, consistency سخت و سخت‌تر میشود.
Facilitates Building Task-based UI
وقتی شما به پیاده سازی الگوی CQRS میپردازید، اصولا هر عملی که توسط End user از طریق ui ارسال میشود، معادل command مربوط به آن وجود دارد. به همین جهت میتوان عملیات لازم برای اجرای یک پروسه را بصورت واضحی درک کرد.
 Maintenance And Flexibility
هر چند پیاده سازی این مدل سخت خواهد بود، اما در ابعاد وسیع‌تر به دلیل اینکه هر قسمت به صورت مجزایی طراحی شده و اینکه دستورات و رویداد‌ها به صورت تفکیک شده پیاده سازی شده‌اند، همچنین وجود ES، قابلیت زیادی به debug سیستم می‌دهد.
نکته: ES مدل مورد قبولی برای اکثر معماری‌های نوین سیستم‌های نرم افزاری امروزی میباشد و فقط مختص به CQRS نمیباشد. بطور مثال در معماری Microservices به وفور از Event Sourcing استفاده میشود.

مشکلات استفاده از این مدل
  • ذاتا پیاده سازی این مدل سخت و دشوار است و از آنجاییکه سادگی در پیاده سازی سیستم‌های نرم افزاری، یک اصل مهم محسوب میشود، بنابراین استفاده از این مدل محدود میشود به سیستم‌های نرم افزاری که مزیت‌های گفته شده در قسمت فوق برایشان حیاتی محسوب شود.
  • برای پیاده سازی سیستمی با این مدل احتیاج به تیم توسعه‌ای است که با مفاهیم آن کاملا آشنا باشد.
  • هر چند امروزه فضای فیزیکی برای ذخیره سازی دیتا ارزان محسوب میشود، اما به هر حال استفاده از این مدل به همراه ES، حجم زیادی از Disk space را خواهد گرفت.
  • همانطور که دیدید برای پیاده سازی یک Insert ساده، حجم زیادی کد نوشته شده‌است. بنابراین تولید اینگونه نرم افزار‌ها به زمان بیشتری نیاز دارد.
بنابراین باید در انتخاب معماری سیستم بسیار دقت شود؛ هر چند که این مدل برای سیستم‌های بزرگ و پیچیده خیلی کارآمد محسوب میشود و باعث یک Domain object غنی ، History Tracking، شفافیت در مشکلات Concurrency و همچنین Scalability و غیره خواهد شد، اما پیدا کردن برنامه نویسانی با داشتن درک عمیق روی این مباحث کمی سخت به نظر میرسد.
در قسمت بعدی بصورت کامل به پیاده سازی این الگو در یک اپلیکشن دات نتی خواهیم پرداخت.
مطالب
Blazor 5x - قسمت 27 - برنامه‌ی Blazor WASM - کار با سرویس‌های Web API
در قسمت‌های Blazor Server مثال این سری، با روش کار با سرویس‌های سمت سرور برنامه، آشنا شدیم. در این نوع برنامه‌ها، فقط کافی است اصل سرویس مدنظر را مستقیما در کامپوننت‌های Razor تزریق کرد و سپس می‌توان به نحو متداولی با آن‌ها کار کرد؛ اما در برنامه‌های Blazor WASM خیر! به این نوع برنامه‌های سمت کلاینت باید همانند برنامه‌های React ، Angular ، Vue و یا حتی برنامه‌های مبتنی بر jQuery نگاه کرد. در تمام فناوری‌های سمت کلاینت، این درخواست‌های Ajax ای هستند که با سرویس‌های یک Web API سمت سرور، ارتباط برقرار کرده، اطلاعاتی را به آن‌ها ارسال و یا دریافت می‌کنند. در برنامه‌های Blazor WASM نیز باید به همین ترتیب عمل کرد و در اینجا HttpClient دات نت، جایگزین برای مثال jQuery Ajax ، Fetch API و یا XMLHttpRequest استاندارد می‌شود (البته jQuery Ajax در اصل یک محصور کننده‌ی استاندارد XMLHttpRequest است که برای اولین بار توسط مایکروسافت در برنامه‌ی Outlook web access معرفی شد).


ایجاد سرویس سمت کلاینت دریافت اطلاعات اتاق‌ها از Web API

در قسمت 24، HotelRoomController را تکمیل کردیم که کار آن، بازگشت اطلاعات تمام اتاق‌ها و یا یک اتاق مشخص به کلاینت است. اکنون می‌خواهیم در ادامه‌ی قسمت قبل، اگر کاربری بر روی دکمه‌ی Go صفحه‌ی اول رزرو اتاقی کلیک کرد، لیست تمام اتاق‌های تعریف شده را به او نمایش دهیم. به همین جهت نیاز به سرویس سمت کلاینتی داریم که بتواند با این Web API endpoint کار کند:
namespace BlazorWasm.Client.Services
{
    public interface IClientHotelRoomService
    {
        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate);
        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate);
    }
}
این سرویس را در پوشه‌ی Services پروژه‌ی BlazorWasm.Client ایجاد کرده‌ایم که HotelRoomDTO خود را از پروژه‌ی BlazorServer.Models دریافت می‌کند. به این ترتیب می‌توان مدلی را بین یک Web API سمت سرور و یک سرویس سمت کلاینت، به اشتراک گذاشت. بنابراین پروژه‌ی کلاینت، باید ارجاعی را به پروژه‌ی BlazorServer.Models.csproj نیز داشته باشد.

در ادامه اینترفیس فوق را به صورت زیر پیاده سازی می‌کنیم:
namespace BlazorWasm.Client.Services
{
    public class ClientHotelRoomService : IClientHotelRoomService
    {
        private readonly HttpClient _httpClient;

        public ClientHotelRoomService(HttpClient httpClient)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        }

        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate)
        {
            throw new NotImplementedException();
        }

        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate)
        {
            // How to url-encode query-string parameters properly
            var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, "/api/hotelroom"))
                            .AddParameter("checkInDate", $"{checkInDate:yyyy'-'MM'-'dd}")
                            .AddParameter("checkOutDate", $"{checkOutDate:yyyy'-'MM'-'dd}")
                            .Uri;
            return _httpClient.GetFromJsonAsync<IEnumerable<HotelRoomDTO>>(uri);
        }
    }
}
توضیحات:
- HttpClient یکی از سرویس‌های تنظیم شده‌ی در فایل Program.cs پروژه‌های سمت کلاینت است. بنابراین می‌توان آن‌را از طریق تزریق به سازنده‌ی این سرویس، به دست آورد.
- در اینجا برای دریافت اطلاعات JSON دریافتی از سمت سرور و سپس Deserialize خودکار آن به لیستی از DTO تعریف شده، از متد جدید GetFromJsonAsync استفاده شده‌است. این مورد جزو تازه‌های NET 5x. است.
- در اینجا استفاده از کلاس UriBuilderExt را نیز جهت تشکیل یک URL دارای کوئری استرینگ، مشاهده می‌کنید. هیچگاه نباید URL نهایی را از طریق جمع زدن اجزای آن به سمت سرور ارسال کرد؛ از این جهت که اجزای آن باید URL-encoded شوند؛ وگرنه در سمت سرور قابلیت پردازش نخواهند داشت. در ادامه تعریف کلاس جدید UriBuilderExt را مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using BlazorServer.Models;
using BlazorWasm.Client.Utils;

using System;
using System.Collections.Specialized;
using System.Web;

namespace BlazorWasm.Client.Utils
{
    public class UriBuilderExt
    {
        private readonly NameValueCollection _collection;
        private readonly UriBuilder _builder;

        public UriBuilderExt(Uri uri)
        {
            _builder = new UriBuilder(uri);
            _collection = HttpUtility.ParseQueryString(string.Empty);
        }

        public UriBuilderExt AddParameter(string key, string value)
        {
            _collection.Add(key, value);
            return this;
        }

        public Uri Uri
        {
            get
            {
                _builder.Query = _collection.ToString();
                return _builder.Uri;
            }
        }
    }
}
- در اینجا توسط متد AddParameter، کار افزودن کوئری استرینگ‌ها به یک Url از پیش مشخص، انجام می‌شود. کار encoding نهایی به صورت خودکار توسط HttpUtility استاندارد دات نت، انجام خواهد شد.
- تاریخ‌های ارسالی به سمت سرور را با فرمت yyyy'-'MM'-'dd تبدیل رشته کردیم. این قالب، یکی از قالب‌های پذیرفته شده‌است.
- جهت سهولت استفاده‌ی از سرویس فوق و همچنین مدل‌های برنامه، فضای نام آن‌ها را به فایل BlazorWasm.Client\_Imports.razor اضافه می‌کنیم تا در تمام کامپوننت‌های برنامه‌ی سمت کلاینت، قابل دسترسی شوند:
@using BlazorWasm.Client.Services
@using BlazorServer.Models
- در آخر این سرویس جدید را باید به لیست سرویس‌های برنامه معرفی کرد تا قابلیت تزریق در کامپوننت‌ها را پیدا کند:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...

            builder.Services.AddScoped<IClientHotelRoomService, ClientHotelRoomService>();

            // ...
        }
    }
}

چند اصلاح جزئی در کنترلرها و سرویس‌های سمت سرور

در Url نهایی فوق، دو پارامتر جدید checkInDate و checkOutDate هم وجود دارند. به همین جهت این دو را به اکشن متدهای کنترلر HotelRoom:
namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HotelRoomController : ControllerBase
    {
        // ...

        [HttpGet]
        public async Task<IActionResult> GetHotelRooms(DateTime? checkInDate, DateTime? checkOutDate)
        {
          // ...
        }

        [HttpGet("{roomId}")]
        public async Task<IActionResult> GetHotelRoom(int? roomId, DateTime? checkInDate, DateTime? checkOutDate)
        {
           // ...
        }
    }
}
و همچنین سرویس سمت سرور IHotelRoomService نیز اضافه می‌کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService : IDisposable
    {
        Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDate, DateTime? checkOutDate);
        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate);
        // ...
    }
}
البته فعلا پیاده سازی خاصی ندارند و آن‌ها را در قسمت‌های بعد مورد استفاده قرار خواهیم داد.


تنظیمات ویژه‌ی HttpClient برنامه‌ی سمت کلاینت

سرویس ClientHotelRoomService فوق، از HttpClient تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند که BaseAddress خود را مطابق تنظیمات ابتدایی برنامه، از HostEnvironment دریافت می‌کند. در اینجا علاقمندیم تا بجای این تنظیم پیش‌فرض، فایل جدید appsettings.json را به پوشه‌ی BlazorWasm.Client\wwwroot\appsettings.json کلاینت اضافه کرده (محل قرارگیری آن در برنامه‌های سمت کلاینت، داخل پوشه‌ی wwwroot است و نه در داخل پوشه‌ی ریشه‌ی اصلی پروژه):
{
    "BaseAPIUrl": "https://localhost:5001/"
}
و از این تنظیم جدید به عنوان BaseAddress برنامه‌ی Web API استفاده کنیم که روش آن‌را در کدهای ذیل مشاهده می‌کنید:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ... 

            // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddScoped(sp => new HttpClient
            {
                BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"))
            });

            // ... 
        }
    }
}

تکمیل کامپوننت دریافت لیست تمام اتاق‌ها

در قسمت قبل، کامپوننت خالی HotelRooms.razor را تعریف کردیم. کاربران پس از کلیک بر روی دکمه‌ی Go صفحه‌ی اول، به این کامپوننت هدایت می‌شوند. اکنون می‌خواهیم، لیست تمام اتاق‌ها را در این کامپوننت، از Web API برنامه دریافت کنیم:
@page "/hotel/rooms"

@inject ILocalStorageService LocalStorage
@inject IJSRuntime JsRuntime
@inject IClientHotelRoomService HotelRoomService

<h3>HotelRooms</h3>

@code {
    HomeVM HomeModel = new HomeVM();
    IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var model = await LocalStorage.GetItemAsync<HomeVM>(ConstantKeys.LocalInitialBooking);
            if (model is not null)
            {
                HomeModel = model;
            }
            else
            {
                HomeModel.NoOfNights = 1;
            }
            await LoadRooms();
        }
        catch (Exception e)
        {
            await JsRuntime.ToastrError(e.Message);
        }
    }

    private async Task LoadRooms()
    {
        Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate);
    }
}
در اینجا در ابتدا سعی می‌شود تا HomeModel، از Local Storage که در قسمت قبل آن‌را تنظیم کردیم، خوانده شود. سپس با استفاده از متد GetHotelRoomsAsync، لیست اتاق‌ها را از Web API دریافت می‌کنیم. تمام این عملیات آغازین نیز باید در روال رویدادگران OnInitializedAsync صورت گیرند.


روش اجرای پروژه‌های Blazor WASM

تا اینجا اگر برنامه‌ی سمت کلاینت را توسط دستور dotnet watch run اجرا کنیم، هرچند صفحه‌ی خالی نمایش لیست اتاق‌ها ظاهر می‌شود، اما یک خطای fetch error را هم دریافت خواهیم کرد؛ چون نیاز است ابتدا پروژه‌ی Web API را اجرا کرد و سپس پروژه‌ی WASM را.
برای ساده سازی اجرای همزمان این دو پروژه، اگر از ویژوال استودیوی کامل استفاده می‌کنید، بر روی نام Solution کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی «Set Startup projects» را انتخاب کنید. در صفحه دیالوگ ظاهر شده، گزینه‌ی «multiple startup projects» را انتخاب کرده و از لیست پروژه‌های موجود، دو پروژه‌ی Web API و WASM را انتخاب کنید و Action مقابل آن‌ها را به Start تنظیم کنید. در اینجا حتی می‌توان ترتیب اجرای این پروژه‌ها را هم تغییر داد. در این حالت زمانیکه بر روی دکمه‌ی Run، در ویژوال استودیو کلیک می‌کنید، هر دو پروژه را با هم برای شما اجرا خواهد کرد.

نکته‌ی مهم! در این حالت هم برنامه‌ی کلاینت نمی‌تواند با برنامه‌ی Web API ارتباط برقرار کند! چون شماره پورت iisExpress درج شده‌ی در فایل appsettings.json آن، باید به شماره sslPort مندرج در فایل Properties\launchSettings.json پروژه‌ی Web API تغییر داده شود که برای نمونه در اینجا این عدد 44314 است:
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:62930",
      "sslPort": 44314
    }
  }
}
و یا اگر می‌خواهید پروژه را از طریق NET Core CLI. با اجرای دستور dotnet watch run اجرا کنید ... به صورت پیش‌فرض نمی‌شود! چون برای اینکار باید به پوشه‌ی ریشه‌ی پروژه‌های Web API و WASM وارد شد و دوبار دستور یاد شده را به صورت مجزا اجرا کرد. در این حالت، هر دو پروژه، بر روی پورت پیش‌فرض 5001 اجرا می‌شوند. روش تغییر این پورت، مراجعه به فایل Properties\launchSettings.json این پروژه‌ها است. برای مثال همان پورت پیش‌فرض 5001 را که در فایل appsettings.json انتخاب کردیم، ثابت نگه می‌داریم. یعنی فایل launchSettings.json پروژه‌ی Web API را ویرایش نمی‌کنیم. اما این پورت را در پروژه‌ی کلاینت برای مثال به عدد 5002 تغییر می‌دهیم تا برنامه‌ی کلاینت، بر روی پورت پیش‌فرض برنامه‌ی Web API اجرا نشود:
{
    "BlazorWasm.Client": {
      "applicationUrl": "https://localhost:5002;http://localhost:5003",
    }  
}


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-27.zip
مطالب
Performance در AngularJS قدم دوم
در مقاله‌ی قبل روش درست استفاده کردن از Binding را برای بهبود Performance، توضیح دادم. در این مقاله می‌خواهم در مورد ng-if و فرق آن با ng-show صحبت کنم و اینکه کدامیک Performance بهتری را برای AngularJS فراهم می‌کنند.

سول اول، کار ng-show چیست؟
ng-show یکی از پر کاربردترین Directiveهای AngularJS است که وظیفه‌ی Show و Hide قسمتی از Vew را به عهده دارد. به کد زیر توجه کنید:
<div ng-show="has">
     <div ng-repeat="item in items">
          <span>{{item.title}}</span>
     </div>
</div>
در این کد ما از ng-show استفاده کرده‌ایم. اگر has مقدار true داشته باشد div نمایش داده می‌شود و اگر false باشد، div نمایش داده نمی‌شود. در داخل div، لیستی وجود دارد که توسط ng-repeat تکرار شده و یک لیست ساده را درست می‌کند.

سول دوم، کار ng-if چیست؟  
کد بالا را دوباره تکرار می‌کنیم. ولی با این تفاوت که اینبار بجای ng-show از ng-if استفاده خواهیم کرد:
<div ng-if="has">
     <div ng-repeat="item in items">
          <span>{{item.title}}</span>
     </div>
</div>
خوب؟ آیا عملکرد این دو کد با هم تفاوت دارد؟ جواب سؤال در ظاهر خیر هست. یعنی مانند کد بالایی، اگر has مقدار true داشته باشد، div نمایش داده می‌شود؛ در غیر اینصورت، خیر.

سوال سوم، پس اگر عملکرد یکسانی دارند، تفاوت آن‌ها در چیست؟
تفاوت این دو Directive در Performance هست. اجازه دهید بیشتر توضیح دهم. ng-show اگر مقدار false دریافت کند، tag مورد نظر را نمایش نمی‌دهد؛ ولی تگ‌های داخلی آن توسط AngularJS پردازش می‌شوند. یعنی چه ng-show مقدار true بگیرید و یا false، لیست داخل tag، توسط AngularJS پردازش و Render می‌شود. ولی در ظاهر ما در View چیزی را نمی‌بینیم. ng-if بر حسب مقادیری که دریافت می‌کند، می‌تواند به بالا رفتن Performance AngularJS کمک کند. فرض کنید ng-if مقدار false گرفته است؛ یعنی has مقدار false دارد. علاوه بر اینکه tag div نمایش داده نمی‌شود، بلکه داخل tag نیز پردازش نمی‌شود. یعنی لیستی که ما در کد نوشته‌ایم، به هیچ عنوان توسط AngularJS پردازش نخواهد شد که باعث می‌شود Watcher، کار کمتری انجام دهد. پس در نتیجه بهبودی را در کارآیی Rendering و Binding خواهیم داشت.
مطالب
امکان تغییر رشته‌ی اتصالی به بانک اطلاعاتی در EF Core در زمان اجرای برنامه
تغییر پویای رشته‌ی اتصالی به بانک اطلاعاتی در نگارش‌های پیشین EF، مشکل بودند که نمونه‌هایی از آن را پیشتر در مطالب زیر مشاهده کرده‌اید:
- «تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد»
- «استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First»

اما EF Core نه تنها این مشکل را پوشش را داده‌است، بلکه امکان تزریق وابستگی‌ها و استفاده‌ی از سرویس‌های مختلف را نیز در این حین، پیش بینی کرده‌است که در ادامه جزئیات آن‌را مرور می‌کنیم.


نیاز به تغییر رشته‌ی اتصالی به بانک اطلاعاتی در زمان اجرا

دلایل نیاز به امکان تغییر رشته‌ی اتصالی در زمان اجرا شامل موارد زیر هستند:
- در برنامه‌هایی کمی پیچیده‌تر و سابقه دار، ممکن است عملیات تجاری یکسال را در بانک اطلاعاتی سال 98 و دیگری را در بانک اطلاعاتی سال 99 ثبت کنید. در این حالت کاربران باید بتوانند در زمان اجرا به هر بانک اطلاعاتی که پیشتر با آن کار کرده‌اند، متصل شده و از آن استفاده کنند.
- یکی از روش‌های پیاده سازی برنامه‌های چند مستاجری، داشتن یک بانک اطلاعاتی مجزا، به ازای هر مستاجر است. در این حالت نیز تک برنامه‌ی ما باید بتواند بر اساس Id مشتری، بانک اطلاعاتی متناظری را در زمان اجرا انتخاب کند.
- نیاز به داشتن چندین context در برنامه و کار با بانک‌های اطلاعاتی متفاوت در زمان اجرا؛ مانند کار با SQL Server، اوراکل و یا SQLite


روش تغییر رشته‌ی اتصالی به بانک اطلاعاتی در EF Core در زمان اجرای برنامه

اگر به روش ثبت متداول سرویس DbContext برنامه و پروایدر آن دقت کنیم:
services.AddDbContext<ApplicationDbContext>(options =>
      options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
));
یک action delegate قابل مشاهده‌است. کار این اکشن، تنظیم پروایدر و تمام نیازهای یک رشته‌ی اتصالی به بانک اطلاعاتی، جهت شروع به کار با Context برنامه است. نکته‌ی مهمی که در اینجا وجود دارد، فراخوانی هرباره‌ی این action، به ازای هر اتصال تشکیل شده‌است. یعنی کدهای داخل این action delegate کش نمی‌شوند و همین مساله امکان تغییر پویای آن‌ها را میسر می‌کند.

یک نکته: چون این اطلاعات کش نمی‌شوند، اگر رشته‌ی اتصالی شما ثابت است (و نیازی به تغییر آن در زمان اجرای برنامه نیست)، محل تامین آن‌را به پیش از سطر services.AddDbContext انتقال دهید و فقط نتیجه‌ی محاسبه شده‌ی نهایی را استفاده کنید تا کارآیی برنامه افزایش یابد؛ در غیراینصورت فراخوانی Configuration.GetConnectionString مدام تکرار خواهد شد.


دریافت یک قالب قابل تغییر از تنظیمات برنامه و تغییر آن با هدرهای درخواست رسیده‌ی به آن

فرض کنید قالب رشته‌ی اتصالی برنامه در فایل appsettings.json به صورت زیر است:
"ConnectionStrings": {
    "ConnectionTemplate": "Data Source=.;Initial Catalog={db_Name};Integrated Security=True",
}
و db_Name آن قرار است برای مثال از یک query string، سشن، کوکی و یا فیلد خاصی در هدر HTTP رسیده تامین شود. برای مثال سال مالی انتخابی و یا شماره مستاجر انتخابی به صورت یک فیلد خاص HTTP به سمت برنامه ارسال می‌شوند.
بنابراین اکنون نیاز است به ازای هر درخواست رسیده بتوان به سرویس IHttpContextAccessor و شیء HttpContext.Request جاری دسترسی یافت و سپس از هدرهای رسیده، برای مثال هدر ویژه‌ی tenantId و یا year را پردازش کرد؛ اما در تعریف services.AddDbContext فوق چگونه می‌توان اینکار را انجام داد؟
خوشبختانه متد services.AddDbContext، دارای یک overload دیگر نیز هست که امکان دسترسی به تمام سرویس‌های جاری سیستم را میسر می‌کند:
services.AddDbContext<ApplicationDbContext>((serviceProvider, dbContextBuilder) =>
{
   var connectionStringTemplate = Configuration.GetConnectionString("ConnectionTemplate");
   var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
   var dbName = httpContextAccessor.HttpContext.Request.Headers["tenantId"].First();
   var connectionString = connectionStringTemplate.Replace("{db_Name}", dbName);
   dbContextBuilder.UseSqlServer(connectionString);
});
همانطور که مشاهده می‌کنید، overload دوم متد services.AddDbContext، امکان ارسال serviceProvider را نیز به این action delegate دارد. پس از آن می‌توان توسط متد GetRequiredService آن به هر سرویس مدنظری که در سیستم ثبت شده‌است، دسترسی یافت و برای مثال در اینجا فیلد هدر سفارشی tenantId را از آن استخراج نمود و در قالب رشته‌ی اتصالی به بانک اطلاعاتی، در زمان اجرا به صورت پویایی جایگزین کرد.
همچنین در صورت نیاز می‌توان UseSqlServer آن‌را نیز در این action delegate به هر پروایدر دیگری در زمان اجرا تغییر داد و از این لحاظ محدودیتی وجود ندارد.

یک نکته: البته برنامه نباید هر tenantId ای را پردازش کند و این خودش می‌تواند تبدیل به یک نقیصه‌ی امنیتی شود. به همین جهت برای مثال می‌توان tenantId را در یک JWT قرار داد و در حین تعیین اعتبار آن و کاربر جاری، این مقدار را نیز بررسی کرد.