مطالب
Content Negotiation در WCF
Content Negotiation ، مکانیزمی است که طی آن مصرف کننده یک سرویس http تعیین می‌کند که خروجی مورد نظر از سرویس به چه فرمتی در اختیار آن قرار گیرد. این قابلیت بسیار زیبا در Asp.Net Web Api فراهم می‌باشد. اما از آن جا که در  WCF به صورت توکار مکانیزمی جهت پیاده سازی این قابلیت در نظر گرفته نشده است می‌توان از طریق یک کتابخانه ثالث به نام WCFRestContrib به این مهم دست یافت.

به صورت معمول برای پیاده سازی Content Negotiation، مصرف کننده باید در Accept هدر درخواست، برای سرویس مورد نظر، نوع Content-Type را نیز تعیین نمایید. از طرفی سرویس دهنده نیز باید معادل Mime Type درخواست شده، یک Formatter جهت سریالایز داده‌ها در اختیار داشته باشد. در WCF از طریق کتابخانه WcfRestContrib می‌توانیم به صورت زیر Content Negotiation را پیاده سازی نماییم:

ابتدا از طریق Nuget کتابخانه زیر را نصب کنید:
install-package WcfRestContrib
حال فرض کنید سرویسی به صورت زیر داریم:
[ServiceContract]
public interface IBooksService
{    
    [OperationContract]
    void AddBook(string isbn, Book book);
}
کد‌های بالا روشی مرسوم برای تعریف Service Contract‌های WCF است. برای اینکه سرویس WCF بالا به صورت Rest طراحی شود و از طرفی قابلیت سریالاز داده‌ها به چندین فرمت را داشته باشد باید به صورت زیر عمل نماییم:
[ServiceContract]
public interface IBooksService
{
    [WebInvoke(UriTemplate = "/{isbn}", Method=Verbs.Put)]
    [WebDispatchFormatter]
    [OperationContract]
    void AddBook(string isbn, Book book);
    ....
}
وظیفه WebDispatchFormatterAttribute تعریف شده برای Operation بالا این است که نوع فرمت مورد نیاز را از Accept هدر درخواست واکشی کرده و با توجه به MimeType‌های تعریف شده در سرویس، داده‌ها را به آن فرمت سریالاز نماید. در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده بود، MimeType پیش فرض انتخاب می‌شود.
گام بعدی مشخص کردن انواع MimeType‌ها برای این سرویس است. در WcfRestContrib به صورت پیش فرض چهار Formatter تعبیه شده است:
»Xml : از DataContractSerializer موجود در WCF برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
»Json : از طریق DataContactJsonSerializer برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
POX : همانند مورد اول از DataContractSerializer استفاده می‌کند با این تفاوت که DataContract‌ها بدون Namesapce و Attribute و DataMember‌ها نیز بدون Order می‌باشند.
»Form Url Encoded

در صورتی که نیاز به formatter دیگری دارید می‌توانید با استفاده از CustomFormatter موجود در این کتابخانه، Formatter دلخواه خود را پیاده سازی نمایید.

همان طور که در بالا ذکر شد، در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده باشد، MimeType پیش فرض انتخاب می‌شود. برای تعریف MimeType پیش فرض می‌توان از خاصیت WebDispatchFormatterConfigurationAttribute که در فضای نام  WcfRestContrib.ServiceModel.Description  قرار دارد استفاده کرد. تعاریف سایر MimeType‌ها نیز با استفاده از WebDispatchFormatterMimeTypeAttribute انجام می‌شود. به صورت زیر:

[WebDispatchFormatterConfiguration("application/xml")]
[WebDispatchFormatterMimeType(typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoDataContract), "application/xml",  "text/xml")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson),  "application/json")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded), "application/x-www-form-urlencoded")]
public class Books : IBooksService 
{    
   public void AddBook(string isbn, Book book)
   {
   }
}
همانند سایر تنظیمات WCF می‌توان تمامی این موارد را در فایل Config پروژه سرویس نیز تعریف کرد: برای مثال:
<system.serviceModel>
    <extensions>
        <behaviorExtensions>
            <add name="webFormatter" 
                 type="WcfRestContrib.ServiceModel.Configuration.WebDispatchFormatter.ConfigurationBehaviorElement, WcfRestContrib, 
                       Version=x.x.x.x, Culture=neutral, PublicKeyToken=89183999a8dc93b5"/>
        </behaviorExtensions>
    </extensions>
    <serviceBehaviors>
        <behavior name="Rest">
          <webFormatter>
            <formatters defaultMimeType="application/xml">
              <formatter mimeTypes="application/xml,text/xml" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoxDataContract, WcfRestContrib"/>
              <formatter mimeTypes="application/json" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson, WcfRestContrib"/>
              <formatter mimeTypes="application/x-www-form-urlencoded" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded, WcfRestContrib"/>
            </formatters>
          </webFormatter>
        </behavior>
    </serviceBehaviors>
</system.serviceModel>
نکته:
در صورتی که قصد داشته باشیم که باتوجه به direction مورد نظر (نظیر Outgoing یا Incoming) داده‌ها سریالایز/ دی سریالایز شوند، می‌توان این مورد را در هنگام تعریف OperationContract تعیین کرد:
[WebDispatchFormatter(WebDispatchFormatter.FormatterDirection.Outgoing)]  

مطلب تکمیلی:

  مشاهده پیاده سازی Content Negotiation در Asp.Net MVC
مطالب
Portable Class Library چیست و چگونه از آن استفاده کنیم؟
Portable Class Library چیست؟
Portable Class Library برای تولید Assembly مدیریت شده که قابیلت استفاده در اکثر تکنولوژی‌ها نظیر (WP7(Windows Phone 7 و WPF و Silverlight و Windows Store App و حتی XBox را داراست استفاده می‌شود.  این نوع پروژه‌ها میزان استفاده مجدد از کد‌ها رو به حداکثر ممکن می‌رسونه و در عوض تعداد پروژه‌های مورد نیاز رو به حداقل. مورد اصلی استفاده از اون‌ها برای تولید Multi-Targeted Application هاست. برای مثال در تولید پروژه‌های تحت Windows که با تکنولوژی WPF پیاده سازی می‌شوند پروژه ViewModel و  Model رو با این تکنولوژی پیاده سازی می‌کنند تا در آینده اگر نیاز بود قسمتی از پروژه با استفاده از Silverlight یا Windows Store App انجام بشه بتونیم Model , ViewModel نوشته شده رو به این پروژه‌ها Reference بدیم. تا قبل از برای انجام این کار باید این دو بخش رو دوباره برای هر تکنولوژی بازنویسی می‌کردیم.
روش استفاده
زمانی که یک پروژه از نوع Portable Class Library رو ایجاد می‌کنیم باید حداقل 2 یا چند تا از Platform‌های مورد نظر رو انتخاب کنیم. به صورت زیر :


بعد از انتخاب گزینه Portable Class Library و زدن کلید Enter  فرم زیر ظاهر خواهد شد که در این قسمت باید Platform‌های مورد نظر رو انتخاب کنیم.


در اینجا من 2 Platform رو انتخاب کردم. یکی Silverlight و Net 4.5. یعنی این Portable Class Library هم می‌تونه به پروژه Silverlight و هم به Class library .Net 4.5 رفرنس داده بشه.
حتما می‌پرسید چه طوری؟
در واقع Microsoft برای انجام این کار فقط یک سری از Reference‌های مشترک بین این 2 Platform رو به پروژه اضافه میکنه و همین موضوع باعث شده کار با این پروژه‌ها نیاز به یکم مهارت داشته باشه.
برای مثال اگر قصد استفاده از تکنولوژی Async&Await  رو دارید باید یکم به خودتون زحمت بدید و CTP مورد نظر رو نصب کنید.(حالا اگر از Net4 استفاده می‌کنید که باید از یک روش دیگه استفاده کنید که در یک مقاله جداگانه براتون توضیح خواهم داد).
به جدول زیر دقت کنید.
 Xbox 360  Windows Phone Silverlight 
Windows Store 
.NET Framework 
 Feature 
 
 
 √  √  
 Core 
  
 √  √  √  LINQ 
 Only 7.5 
 √  √  
 IQueryable 
   
 
 Only 4.5  Dynamic keyword 
   √  
 √  Managed Extensibility Framework (MEF) 
  √  √  
 √  Network Class Library (NCL) 
  
 √  
 √  Serialization 
  
 √  
 √  Windows Communication Foundation (WCF) 
  √  √  
  Only 4.5 
 
 Model-View-View Model (MVVM) 
   
 
 Only 4.0.3 and 4.5  Data annotations 
 √  √  
 
 Only 4.0.3 and 4.5  XLINQ 

در جدول بالا به صورت کامل مشخص شده که در هر پلاتفرم چه چیزی Support میشه. برای مثال Data Annotation فقط در Net4.3 , 4.5  قابل استفاده است.
اسمبلی‌های زیر در یک Portable Class Library  قابل استفاده هستند.
  • mscorlib.dll

  • System.dll

  • System.Core.dll

  • System.Xml.dll

  • System.ComponentModel.Composition.dll

  • System.Net.dll

  • System.Runtime.Serialization.dll

  • System.ServiceModel.dll

  • System.Xml.Serialization.dll

  • System.Windows.dll  - From Silverlight

در صورتی که از VS2010 استفاده می‌کنید می‌تونید از این جا Portable Library Tools رو دانلود کنید. بعد از نصب دقیقا همین مراحل رو برای ایجاد پروژه انجام بدید.
پیاده سازی یک مثال عملی
در این مثال قصد دارم یک کلاس برای مدیریت تاریخ شمسی درست کنم. مراحل زیر رو دنبال کنید
 یک Portable Class Library  به نام Common به پروژه اضافه کنید.
یک کلاس به نام PersianDate به برنامه اضافه کرده به صورت زیر
 public class PersianDate<TCalendar> where TCalendar : System.Globalization.Calendar
    {
        public PersianDate()
        {
            this.CurentCalendar = System.Activator.CreateInstance<TCalendar>();
        }

        public TCalendar CurentCalendar 
        {
            get;
            private set;
        }

        public string GetDate()
        {
            return string.Format( "{0}/{1}/{2}", this.CurentCalendar.GetYear( DateTime.Today ).ToString( "####0000" )
                , this.CurentCalendar.GetMonth( DateTime.Today ).ToString( "##00" )
                , this.CurentCalendar.GetDayOfMonth( DateTime.Today ).ToString( "##00" ) );
        }
    }
کلاس بالا به صورت Generic تعریف شده که شما باید هنگام استفاده حتما یک نوع Calendar رو بهش بدید. دلیل این کار اینه که کلاس PersianCalendar در Portable Class Library وجود ندارد و فقط یک کلاس پایه Calendar در فضای نام System.Globalization وجود داره.

حالا یک پروژه از نوع Console Application به برنامه اضافه کنید و یک Reference  به پروژه Common بدید و بعد کلاس زیر رو بنویسید.
public class CustomPersianDate
    {
        public CustomPersianDate()
        {

        }

        public static Common.PersianDate<System.Globalization.PersianCalendar> PersianCalendar
        {
            get
            {
                return _persianCalendar ?? ( _persianCalendar = new Common.PersianDate<System.Globalization.PersianCalendar>() );
            }
        }
        private static Common.PersianDate<System.Globalization.PersianCalendar> _persianCalendar;
    }
در این قسمت ما نوع TCalendar رو از نوع PersianCalendar تعیین کردیم.

حالا یک پروژه از نوع Silverlight به برنامه اضافه کنید و یک Reference  به پروژه Common بدید و بعد کلاس بالا بدون تغییر بنویسید.
نمای کلی پروژه باید به صورت زیر باشد.

بعد پروژه ConsoleApplication  رو به عنوان پروژه StartUp انتخاب کنید و فایل Program  رو به صورت زیر تغییر بدید.
class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine( "Today Is ?{0}", CustomPersianDate.PersianCalendar.GetDate() );

            Console.ReadLine();
        }
    }

خوب نتیجه به صورت زیر خواهد بود:

برای پروژه Silverlight هم نتیجه قطعا به همین صورت است.

همان طور که دید انتخاب نوع Calendar به خود Application‌ها واگذار شد و شما می‌تونید هر نوع تقویم رو به عنوان TCalendar تعیین کنید.

امیدوارم شما هم مثل من به این تکنولوژی دلچسب علاقه پیدا کرده باشید.





مطالب
آموزش WAF (بررسی Commandها)
در این پست قصد داریم مثال  قسمت قبل را توسعه داده و پیاده سازی Command‌ها را در آن در طی یک مثال بررسی کنیم. از این جهت دکمه‌ای، جهت حذف آیتم انتخاب شده در دیتا گرید، به فرم BookShell اضافه می‌نماییم. به صورت زیر:
<Button Content="RemoveItem" Command="{Binding RemoveItemCommand}" HorizontalAlignment="Left"  VerticalAlignment="Top" Width="75"/>
Command تعریف شده در Button مورد نظر به خاصیتی به نام RemoveItemCommand در BookViewModel که نوع آن ICommand است اشاره می‌کند. پس باید تغییرات زیر را در ViewModel اعمال کنیم:
public ICommand RemoveItemCommand { get; set; }
از طرفی نیاز به خاصیتی داریم که به آیتم جاری در دیتاگرید اشاره کند.
  public Book CurrentItem 
        {
            get
            {
                return currentItem;
            }
            set
            {
                if(currentItem != value)
                {
                    currentItem = value;
                    RaisePropertyChanged("CurrentItem");
                }
            }
        }
        private Book currentItem;

همان طور که در پست قبلی توضیح داده شد پیاده سازی‌ها تعاریف ViewModel در Controller انجام می‌گیرد برای همین منظور باید تعریف DelegateCommand که یک پیاده سازی خاص از ICommand است در کنترلر انجام شود. :
 [Export]
    public class BookController
    {
        [ImportingConstructor]
        public BookController(BookViewModel viewModel)
        {
            ViewModelCore = viewModel;
        }

        public BookViewModel ViewModelCore
        {
            get;
            private set;
        }

        public DelegateCommand RemoveItemCommand 
        { 
            get; 
            private set;
        }

        private void ExecuteRemoveItemCommand()
        {
            ViewModelCore.Books.Remove(ViewModelCore.CurrentItem);
        }

        private void Initialize()
        {
            RemoveItemCommand = new DelegateCommand(ExecuteRemoveItemCommand);
            ViewModelCore.RemoveItemCommand = RemoveItemCommand;
        }

        public void Run()
        {

            var result = new List<Book>();
            result.Add(new Book { Code = 1, Title = "Book1" });
            result.Add(new Book { Code = 2, Title = "Book2" });
            result.Add(new Book { Code = 3, Title = "Book3" });

            Initialize();

            ViewModelCore.Books = new ObservableCollection<Models.Book>(result);        

            (ViewModelCore.View as IBookView).Show();
        }              
    }

تغییرات:
»خاصیتی به نام RemoveItemCommand که از نوع DelegateCommand است تعریف شده است؛
»متدی به نام Initialize اضافه شد که متد‌های Execute و CanExecute برای Command‌ها را در این قسمت رجیستر می‌کنیم.
»در نهایت Command تعریف شده در کنترلر به Command مربوطه در ViewModel انتساب داده شد.

حال کافیست خاصیت SelectedItem دیتاگرید BookShell به خاصیت CurrentItem موجود در ViewModel مقید شود:
 <DataGrid ItemsSource="{Binding Books}" SelectedItem="{Binding CurrentItem ,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="400" Height="200">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="100"></DataGridTextColumn>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="300"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
اگر پروژه را اجرا نمایید، بعد از انتخاب سطر مورد نظر و کلیک بر روی دکمه RemoveItem مورد زیر قابل مشاهده است:

نظرات مطالب
کنترل نوع‌های داده با استفاده از EF در SQL Server
یک نکته‌ی تکمیلی: ویژگی Unicode در EF-Core 6x
در EF-Core 6x، اگر می‌خواهید نوع ستون رشته‌ای را غیریونیکد، مانند varchar‌ها تعیین کنید، می‌توان از ویژگی جدید Unicode برای انجام اینکار استفاده کرد:
public class Book
{
    public int Id { get; set; }

    public string Title { get; set; }

    [Unicode(false)]
    [MaxLength(22)]
    public string Isbn { get; set; }
}
در این حالت Title به nvarchar(max) ترجمه شده (چون حداکثر طولی برای آن مشخص نشده، طول آن max در نظر گرفته می‌شود) و Isbn به varchar(22)؛ چون اینبار حداکثر طول آن 22 است و همچنین یونیکد هم تعریف نشده‌است.
مزیت اینکار، ترجمه‌ی غیروابسته‌ی به بانک اطلاعاتی، توسط EF-Core است. یعنی بسته به بانک‌های اطلاعاتی مختلف، این ترجمه متفاوت خواهد بود (و نیازی به hard-code کردن نام خاصی در اینجا نیست) و همچنین اگر بانک اطلاعاتی از رشته‌های غیریونیکد پشتیبانی نکند، از ویژگی Unicode صرفنظر خواهد شد.
مطالب
معرفی واژه‌ی کلیدی جدید required در C# 11
واژه‌ی کلیدی جدید required در C# 11.0، همانند خواص init-only که پیشتر معرفی شدند، با هدف آغاز و نمونه سازی دقیق‌تر و ساده‌تر اشیایی است که برای اینکار، به تعاریف ویژه‌ی سازنده‌ی کلاس‌ها وابسته نیستند.


امکان نمونه سازی بدون قید و شرط کلاس‌ها

تعریف کلاس Article1 را به صورت زیر درنظر بگیرید:
public class Article1
{
    public string Title { get; set; }
    public string? Subtitle { get; set; }
    public string Author { get; set; }
    public DateTime Published { get; set; }
}
ساختار پروژه‌های دات نت 7 نیز به صورت پیش‌فرض به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
یعنی nullable reference types در آن‌ها فعال است. با این فعال بودن، به اخطارهای زیر می‌رسیم:
Non-nullable property 'Title' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
Non-nullable property 'Author' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [CS11Tests]csharp(CS8618)
عنوان می‌کند که خاصیت‌های Title و Author، به صورت غیرنال‌پذیر تعریف شده‌اند (و همانند Subtitle نال‌پذیر نیستند)؛ اما تعریف این کلاس به نحوی است که این مساله را الزامی نمی‌کند. یعنی می‌توان نمونه‌ای از Article1 را ایجاد کرد که در آن، هر دوی این خواص نال باشند؛ هرچند در این حالت مشکلی از لحاظ کامپایل وجود نخواهد داشت، اما ممکن است به علت اشتباه استفاده‌ی از آن‌ها، به null reference exceptions برسیم. چون یکی از مهم‌ترین اهداف استفاده از یک چنین تعاریفی و فعال سازی nullable reference type در یک پروژه، ارائه‌ی متادیتای بهتری جهت خواص و پارامترها و خروجی‌های متدهاست تا استفاده کننده دقیقا بداند که آیا این خواص می‌توانند نال باشد یا خیر. اگر  public string ای تعریف شده، یعنی این خاصیت قطعا نال نخواهد بود و می‌توان بدون مشکل و بدون بررسی مقدار آن، از آن استفاده کرد و اگر ?public string ای تعریف شده، یعنی ممکن است مقدار آن نال نیز باشد و بهتر است پیش از استفاده‌ی از آن، حتما مقدار آن بررسی شود. اکنون مشکل اینجا است که هیچگونه قیدی، جهت اجبار به مقدار دهی خواص غیرنال پذیر در اینجا وجود ندارند و می‌توان نمونه‌ای از شیء Article1 را ایجاد کرد که در آن متادیتای خواص غیرنال پذیر تعریفی در آن، نقض شوند.


مدیریت کردن نحوه‌ی نمونه سازی کلاس‌ها، با وابستگی به سازنده‌های آن

یکی از روش‌های مدیریت مشکلی که تا اینجا بررسی شد، تعریف سازنده‌های متعددی برای یک کلاس است؛ تا توسط آن بتوان مقدار دهی یک سری از خواص را اجباری کرد:
public class Article2
{
    public Article2(string title, string subtitle, string author, DateTime published)
    {
        Title = title;
        Subtitle = subtitle;
        Author = author;
        Published = published;
    }

    public Article2(string title, string author, DateTime published)
    {
        Title = title;
        Author = author;
        Published = published;
    }

    public string Title { get; set; }
    public string? Subtitle { get; set; }
    public string Author { get; set; }
    public DateTime Published { get; set; }
}
در این کلاس، نمونه‌ی بهبود یافته‌ی Article1 را مشاهده می‌کنید که استفاده کننده را وادار به مقدار دهی title و author می‌کند. در این حالت اخطارهای کامپایلری را که مشاهده کردید، رفع می‌شوند؛ اما به همراه این مسایل است:
- تعداد سطرهای تعریف این کلاس، به شدت افزایش یافته‌است.
- با اضافه شدن خواص بیشتری به کلاس، به تعاریف بیشتری نیاز خواهد بود.
- سازنده‌ها کار خاصی را بجز نگاشت مقادیر ارائه شده، به خواص کلاس، انجام نمی‌دهند.
- نمونه سازی این کلاس‌ها، شکل طولانی و غیرواضح زیر را پیدا می‌کند و زیبایی inline object initializers را ندارند:
 Article2 article = new("C# 11 Required Keyword", "A new language feature", "Name",  new DateTime(2022, 11, 12));

البته روش دیگر مدیریت یک چنین اخطارهایی، استفاده از مقدار ویژه‌ی !default است که سبب محو اخطارهای یاد شده می‌شود؛ اما باز هم مقدار دهی آن‌را الزامی نمی‌کند. فقط به این معنا است که قول می‌دهیم این خاصیت را در جای دیگری مقدار دهی کنیم و هیچگاه نال نباشد!
 public string Title { get; set; } = default!;


مدیریت کردن نحوه‌ی نمونه سازی کلاس‌ها، بدون وابستگی به سازنده‌های آن در C# 11.0

C# 11 به همراه واژه‌ی کلیدی جدیدی به نام required است تا دیگر نیازی نباشد همانند راه حل فوق، سازنده‌های متعددی را جهت اجبار به مقدار دهی خواص یک شیء، تعریف کنیم. در این حالت تعریف کلاس Article به صورت زیر خلاصه می‌شود و دیگر به همراه اخطارهای کامپایلر نمایش داده شده نیز نیست:
public class Article3
{
    public required string Title { get; set; }
    public string? Subtitle { get; set; }
    public required string Author { get; set; }
    public DateTime Published { get; set; }
}
به این ترتیب هنوز می‌توان از زیبایی و خوانایی به همراه نمونه سازی توسط روش inline object initializers بهره‌مند شد و همچنین مطمئن بود که اگر استفاده کننده خاصیت غیرنال‌پذیر Title را مقدار دهی نکند، اینبار با یک خطای کامپایلر متوقف خواهد شد:



معرفی ویژگی جدید SetsRequiredMembers

کلاس Book زیر را که به همراه یک خاصیت required و دو سازنده‌است، درنظر بگیرید:
public class Book
{
    public Book() => Name = string.Empty;

    public Book(string name) => Name = name;

    public required string Name { get; set; }
}
اکنون فرض کنید که بر این اساس، شیء‌ای را به صورت زیر نمونه سازی کرده‌ایم:
Book book = new("Book's Name");
این قطعه کد با خطای زیر کامپایل نمی‌شود:
Required member 'Book.Name' must be set in the object initializer or attribute constructor. [CS11Tests]csharp(CS9035)
عنوان می‌کند که باید خاصیت Name را حتما مقدار دهی کرد؛ چون از نوع required است. هرچند سازنده‌‌ای که از آن استفاده شده، این مقدار دهی را انجام داده‌است و مشکلی از لحاظ عدم مقدار دهی خاصیت Name در اینجا وجود ندارد. برای رفع این مشکل، باید تغییر زیر را اعمال کرد:
public class Book
{
    [SetsRequiredMembers]
    public Book() => Name = string.Empty;

    [SetsRequiredMembers]
    public Book(string name) => Name = name;

    public required string Name { get; set; }
}
با استفاده از ویژگی جدید SetsRequiredMembers عنوان می‌کنیم که این سازنده‌ی خاص، حتما خواص از نوع required را نیز مقدار دهی می‌کند و نیازی به صدور خطای یاد شده نیست. در این حالت بررسی خواص required توسط کامپایلر غیرفعال می‌شود.


محدودیت‌های کار با خواص required

- واژه‌ی کلیدی required را می‌توان تنها به خواص و فیلدهای نوع‌های class, record, record struct اعمال کرد. امکان اعمال این واژه‌ی کلیدی به اجزای یک اینترفیس وجود ندارد.
- میدان دید اعضای required باید حداقل در حد نوع‌های دربرگیرنده‌ی آن‌ها باشند. برای مثال اگر کلاسی public است، نمی‌توان در آن یک فیلد required با میدان دید protected را تعریف کرد.
- نوع‌های مشتق شده‌ی از یک نوع پایه، نمی‌توانند اعضای required آن‌را مخفی کنند و اگر قصد بازنویسی آن‌را دارند، باید حتما واژه‌ی کلیدی required را لحاظ کنند.
- اگر سازنده‌ای به سازنده‌ی دیگری از طریق ذکر ()base و یا ()this زنجیر شده باشد نیز باید ویژگی SetsRequiredMembers مرتبط را تکرار کند.
مطالب
Dependency Injection در Asp.Net WebApi (روش اول)
طی این پست با تزریق وابستگی‌ها در Asp.net MVC آشنا شدید. روش ذکر شده در آن برای کنترلرهای Web Api جوابگو نیست و باید از روش‌های دیگری برای این منظور استفاده نماییم.

نکته 1: برای پیاده سازی این مثال‌ها، Castle Windsor به عنوان IOC Container انتخاب شده است. بدیهی است می‌توانید از Ioc Container مورد نظر خود نیز بهره ببرید.
نکته 2: می‌توانید از مقاله [هاست سرویس‌های Web Api با استفاده از OWIN و TopShelf] جهت هاست سرویس‌های web Api خود استفاده نمایید.

روش اول

اگر قبلا در این زمینه جستجو کرده باشید، به احتمال زیاد با مفهوم IDependencyResolver بیگانه نیستید. درباره استفاده از این روش مقالات متعددی نوشته شده است؛ حتی در مثال‌های موجود در خود سایت MSDN نیز این روش را مرسوم دانسته و آن را به اشتراک می‌گذارند. جهت نمونه می‌توانید این پروژه را دانلود کرده و کد‌های آن را بررسی کنید.
در این روش، قدم اول، ساخت یک کلاس و پیاده سازی اینترفیس IDependencyResolver می‌باشد؛ به صورت زیر:
public class ApiDependencyResolver : IDependencyResolver
    {
        public ApiDependencyResolver(IWindsorContainer container)
        {
            Container = container;
        }

        public IWindsorContainer Container
        {
            get;
            private set;
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return Container.Kernel.HasComponent(serviceType) ? Container.Resolve(serviceType) : null;
            }
            catch (Kernel.ComponentNotFoundException)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return Container.ResolveAll(serviceType).Cast<object>();
            }
            catch (Kernel.ComponentNotFoundException)
            {
                return Enumerable.Empty<object>();
            }
        }
        public IDependencyScope BeginScope()
        {
            return new SharedDependencyResolver(Container);
        }

        public void Dispose()
        {
            Container.Dispose();
        }
    }
اینترفیس IDependencyResolver از اینترفیس دیگری به نام IDependencyScope ارث می‌برد که دارای دو متد اصلی به نام‌های GetService و GetServices است که جهت وهله سازی کنترلرها استفاده می‌شوند. با فراخوانی این متد‌ها، نمونه‌ی ساخته شده توسط Container بازگشت داده خواهد شد.


کاربرد متد BeginScope چیست
؟

کنترلر‌ها به صورت (Per Request) بر اساس هر درخواست وهله سازی خواهند شد. جهت مدیریت چرخه‌ی عمر کنترلرها و منابع در اختیار آن‌ها، از متد BeginScope استفاده می‌شود. به این صورت که نمونه‌ی اصلی DependencyResolver در هنگام شروع برنامه به GlobalConfiguration پروژه Attach خواهد شد. سپس به ازای هر درخواست، جهت وهله سازی Controller‌ها، متد GetService از محدوده داخلی (منظور فراخوانی متد BeginScope است) باعث ایجاد نمونه و بعد از اتمام فرآیند، متد Dispose باعث آزاد سازی منابع موجود خواهد شد.
پیاده سازی متد BeginScope وابسته به IocContainer مورد استفاده شما است. در این جا  کلاس SharedDependencyResolver را به صورت زیر پیاده سازی کردم:
    public class SharedDependencyResolver : IDependencyScope
    {
        public SharedDependencyResolver(IWindsorContainer container)
        {
            Container = container;
            Scope = Container.BeginScope();
        }

        public IWindsorContainer Container
        {
            get;
            private set;
        }

        public IDisposable Scope
        {
            get;
            private set;
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return Container.Kernel.HasComponent(serviceType) ? Container.Resolve(serviceType) : null;
            }
            catch (ComponentNotFoundException)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return Container.ResolveAll(serviceType).Cast<object>();
            }
            catch (ComponentNotFoundException)
            {
                return null;
            }
        }

        public void Dispose()
        {
            Scope.Dispose();
        }
    }
اگر از UnityContainer استفاده می‌کنید کافیست تکه کد زیر را جایگزین کلاس بالا نمایید:
public IDependencyScope BeginScope()
{
    var child = container.CreateChildContainer();
    return new ApiDependencyResolver(child);
}
برای جستجوی خودکار کنترلرها و  رجیستر کردن آن‌ها به برنامه Windsor امکانات جالبی را در اختیار ما قرار می‌دهد. ابتدا یک Installer ایجاد می‌کنیم:
public class KernelInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {          
           container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestyleTransient());         
            container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel, true));
        }
    }

در پایان در کلاس Startup نیز کافیست مراحل زیر را انجام دهید:
 »ابتدا Installer نوشته شده را به WindsorContainer معرفی نمایید.
»DependencyResolver  نوشته شده را  به HttpConfiguration معرفی کنید.
»عملیات Routing مورد نظر را ایجاد و سپس config مورد نظر را در اختیار appBuilder قرار دهید.
public class Startup
    {
        public void Configuration( IAppBuilder appBuilder )
        {
           var container = new WindsorContainer();
            container.Install(new KernelInstaller());

            var config = new HttpConfiguration
            {
                 DependencyResolver = new ApiDependencyResolver(container)
            };                  

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "Default" ,
                routeTemplate: "{controller}/{action}/{name}" ,
                defaults: new { name = RouteParameter.Optional }                
            );

            config.EnsureInitialized();          
         
            appBuilder.UseWebApi( config );
        }
    }
نکته: این روش به دلیل استفاده از الگوی ServiceLocator و همچنین نداشتن Context درخواست ها  روشی منسوخ شده می‌باشد که طی این مقاله جناب نصیری به صورت کامل به این مبحث پرداخته اند.

مطالب
پیاده سازی پروژه‌ای مبتنی بر CQRS و ES
در قسمت قبلی با معماری CQRS و Event Sourcing بصورت مختصر آشنا شدیم. برای درک بیشتر مطلب پیشین، احتیاج به پیاده سازی آن به صورت عملیاتی و نه فقط تئوری محض میباشد و در این مرحله قصد پیاده سازی این مدل را به ساده‌ترین صورت ممکن داریم.
برای مطالعه‌ی ادامه‌ی این مقاله، نیاز به آشنایی با مباحث مطرح شده در قسمت قبل وجود دارد. پس از توضیحات اضافه بر روی قسمت‌های زیر گذشته و فرض بر آن است که آشنایی با این قسمت‌ها وجود دارد.
از این مدل میتوان در زبان‌های مختلف برنامه نویسی و همچنین سیستم‌های مختلف اعم از وب اپلیکیشن و ... استفاده نمود. همچنین برای استفاده از این مدل نیاز قطعی به استفاده از فریم ورک خاصی نیست. در صورت نیاز میتوانید پیاده سازی سفارشی خاص خود را داشته باشید. اما برای ساده‌تر شدن و هرچه سریعتر شدن مراحل از فریمورک SimpleCqrs استفاده میکنیم. هر چند بر خلاف نامش امکانات فراوانی را در اختیار برنامه نویسان قرار میدهد و حتی در پروژه‌های واقعی نیز میتوان از آن استفاده نمود.
برای سریعتر شدن کار میخواهیم پیاده سازی این مدل را در یک پروژه‌ی Console انجام دهیم و همچنین پس از ایجاد، پکیج‌های زیر را نصب مینماییم:
Unity, SimpleCqrs, SimpleCqrs.Unity
میخواهیم طبق مراحل گفته شده‌ی در قسمت قبل، به پیاده سازی این مدل بپردازیم و هدف، اضافه کردن یک Account به سیستم خواهد بود.
ابتدا باید DomainObject مورد نظر نوشته شود:
using System;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class Account : AggregateRoot
    {
        public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }

        public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }

        public void OnAccountCreated(AccountCreatedEvent evt)
        {
            Id = evt.AggregateRootId;
        }
    }
}
نکته: میخواهیم عملیات اضافه کردن یک Account، با استفاده از دو event مربوطه به نام AccountCreatedEvent و مقدار دهی آن با استفاده از AccountNameSetEvent انجام شود.
eventهای فوق را در ادامه اضافه خواهیم داد (از توضیحات بیشتر صرفنظر شده و به مقاله‌ی قسمت قبل رجوع شود).
حال احتیاج به پیاده سازی Command مربوطه برای انجام وظیفه‌ی خود داریم که هدف آن، اضافه کردن یک Account  به سیستم مورد نظر میباشد.
فرض کنید برای اضافه شدن Account، پراپرتی‌های FirstName و LastName باید مقدار دهی شوند:
using SimpleCqrs.Commanding;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommand : ICommand
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

حال CommandHandler که وظیفه‌ی تفسیر کردن Command مربوطه را به عهده دارد، پیاده سازی خواهد شد:
using System;
using SimpleCqrs.Commanding;
using SimpleCqrs.Domain;

namespace CqrsPattern.Cqrs.Command
{
    public class CreateAccountCommandHandler : CommandHandler<CreateAccountCommand>
    {
        private readonly IDomainRepository repository;

        public CreateAccountCommandHandler(IDomainRepository repository)
        {
            this.repository = repository;
        }

        public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid());
            account.SetName(command.FirstName, command.LastName);

            repository.Save(account);
        }
    }
}
نکته: از طریق account.SetName فراخوانی Event مربوطه انجام شده‌است و همچنین repository.Save به raise کردن EventHandler میپردازد.
event مربوط به اضافه شدن Account را به صورت زیر پیاده سازی مینماییم:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountCreatedEvent : DomainEvent { }
}
و همچنین event مربوط به مقدار دهی پراپرتی‌ها نیز به صورت زیر خواهد بود:
using SimpleCqrs.Eventing;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountNameSetEvent : DomainEvent
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}
در این بخش، پیاده سازی EventHandler را خواهیم داشت. طبق مطلب پیشین هر Domain باید EventHnadler ی داشته باشد که از Event هایش ارث بری کرده و هر کدام از Event‌ها عملا در قسمت Handle مربوط به خودش پردازش خواهد شد.
using System.Linq;
using SimpleCqrs.Eventing;
using CqrsPattern.Cqrs.Db;

namespace CqrsPattern.Cqrs.Command
{
    public class AccountEventHandler : IHandleDomainEvents<AccountCreatedEvent>,
                                             IHandleDomainEvents<AccountNameSetEvent>
    {
        private readonly FakeAccountTable accountTable;

        public AccountEventHandler(FakeAccountTable accountTable)
        {
            this.accountTable = accountTable;
        }

        public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
    }
}
نکته: از آنجاییکه پیاده سازی ذخیره کردن Account با استفاده از دو event فوق انجام شده، بعد از Raise شدن EventHandler هر دو متد Handle، وظیفه‌ی Command مربوطه را به عهده دارند (بنابراین وظیفه‌ی هر Command میتواند با استفاده از event‌های مختلفی انجام شود).
برای اینکه نخواهیم وارد فاز‌های مربوط به دیتابیس شویم، موقتا یک db به صورت fake شده را پیاده سازی مینماییم؛ به صورت زیر:
using System.Collections.Generic;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTable : List<FakeAccountTableRow>
    { }
}
using System;

namespace CqrsPattern.Cqrs.Db
{
    public class FakeAccountTableRow
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
}

و همچنین نیاز به ServiceLocator برای نمونه گرفتن از RunTime ی که از آن ارث بری کرده است داریم (برای سادگی کار از الگوی ServiceLocator استفاده میکنیم، ServiceLocator جز Anti-Pattern  ها محسوب میشود و معمولا در پروژه‌های واقعی از آن استفاده نمیشود)
using SimpleCqrs;
using SimpleCqrs.Unity;

namespace CqrsPattern
{
    public class SampleRunTime : SimpleCqrsRuntime<UnityServiceLocator> { }
}
حال احتیاج به پیاده سازی قسمت Queryداریم به همراه ReadModel و سرویسی برای فراخوانی آن
using System;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReadModel
    {
        public string Name { get; set; }
        public Guid Id { get; set; }
    }
}
using CqrsPattern.Cqrs.Db;
using System.Collections.Generic;
using System.Linq;

namespace CqrsPattern.Cqrs.Query
{
    public class AccountReportReadService
    {
        private FakeAccountTable fakeAccountDb;

        public AccountReportReadService(FakeAccountTable fakeAccountDb)
        {
            this.fakeAccountDb = fakeAccountDb;
        }

        public IEnumerable<AccountReadModel> GetAccounts()
        {
            return from a in fakeAccountDb
                   select new AccountReadModel { Id = a.Id, Name = a.Name };
        }
    }
}

در قسمت Main نرم افزار نیاز به register کردن FakeTable خود داریم و همانطور که ملاحظه میکنید Command مورد نظر را نمونه سازی کرده و آن را روی CommandBus قرار میدهیم تا مراحل پیاده سازی شده در قسمت‌های فوق انجام شود و همچنین بعد از اتمام command ارسال شده از طریق Service مورد نظر اطلاعات ذخیره شده بازگردانی میشود
using System;
using SimpleCqrs.Commanding;
using CqrsPattern.Cqrs.Query;
using CqrsPattern.Cqrs.Command;

namespace CqrsPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var runtime = new SampleRunTime();

            runtime.Start();

            var fakeAccountTable = new FakeAccountTable();
            runtime.ServiceLocator.Register(fakeAccountTable);
            runtime.ServiceLocator.Register(new AccountReportReadService(fakeAccountTable));
            var commandBus = runtime.ServiceLocator.Resolve<ICommandBus>();

            var cmd = new CreateAccountCommand { FirstName = "Ali", LastName = "Kh" };

            commandBus.Send(cmd);

            var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();

            Console.WriteLine("Accounts in database");
            Console.WriteLine("####################");
            foreach (var account in accountReportReadModel.GetAccounts())
            {
                Console.WriteLine(" Id: {0} Name: {1}", account.Id, account.Name);
            }

            runtime.Shutdown();

            Console.ReadLine();
        }
    }
}
اینگونه کل عملیات‌های لازم انجام خواهد شد.

خلاصه:
1) Command مربوطه را نمونه سازی کرده و روی CommandBus قرار میدهیم.
2) CommandHandler فراخوانی شده و فانکشن Handle آن باعث نمونه سازی از AggregateRoot میشود.
public override void Handle(CreateAccountCommand command)
        {
            var account = new Account(Guid.NewGuid()); //line 1
            account.SetName(command.FirstName, command.LastName); //line 2
            repository.Save(account); //line 3
        }
در خط نخست Constructor کلاس Account باعث Apply شدن event مربوطه میشود.
public Account(Guid id)
        {
            Apply(new AccountCreatedEvent { AggregateRootId = id });
        }
و در خط دوم account.SetName  برای Apply شدن event مربوط به مقدار دهی property‌ها میباشد.
public void SetName(string firstName, string lastName)
        {
            Apply(new AccountNameSetEvent { FirstName = firstName, LastName = lastName });
        }
و همچنین در خط  سوم و پس از repository.Save باعث میشود event‌های pending شده Raise شده و توسط متد Handle مربوط به EventHandler پردازش شده و عملیات‌های زیر انجام شوند:
public void Handle(AccountCreatedEvent domainEvent)
        {
            accountTable.Add(new FakeAccountTableRow { Id = domainEvent.AggregateRootId });
        }

        public void Handle(AccountNameSetEvent domainEvent)
        {
            var account = accountTable.Single(x => x.Id == domainEvent.AggregateRootId);
            account.Name = domainEvent.FirstName + " " + domainEvent.LastName;
        }
رکورد مورد نظر ثبت شده و event بعدی، پراپرتی‌هایش را مقدار دهی مینماید  و بصورت InMemory درون FakeAccountTable ذخیره میشود (پر واضح است که در یک پروژه‌ی واقعی به جای ذخیره شدن در یک Collection باید درون دیتایس واقعی ذخیره سازی شود).
و پس از اتمام عملیات انجام شده، بصورت زیر در Main برنامه اطلاعات ذخیره شده بازگردانده خواهد شد:
var accountReportReadModel = runtime.ServiceLocator.Resolve<AccountReportReadService>();
var accounts = accountReportReadModel.GetAccounts();

در ادامه برای مطالعه بیشتر میتوان به Scale out کردن این سیستم و استفاده از فریمورک‌های  messaging چون Redis یا Kafka پرداخت و همچنین اعمال Load Balancing را در اینگونه سیستم‌ها انجام داد.
نکته: Cqrs-Pattern را میتوانید از اینجا clone نمایید
مطالب
لیست کردن ایمیل‌های موجود در Global address list

Global Address List یا به اختصار GAL و یا همان Microsoft Exchange Global Address Book ، حاوی اطلاعات تمامی کاربران تعریف شده در Exchange server مایکروسافت است و زمانیکه outlook در شبکه به exchange server متصل می‌شود، کاربران می‌توانند با کمک آن لیست اعضاء را مشاهده کرده ، یک یا چند نفر را انتخاب نموده و به آن‌ها ایمیل ارسال کنند (شکل زیر):


نیاز بود تا این لیست تعریف شده در مایکروسافت اکسچنج، با اطلاعات یک دیتابیس مقایسه شوند که آیا این اطلاعات مطابق رکوردهای موجود تعریف شده یا خیر.
بنابراین اولین قدم، استخراج email های موجود در GAL بود (دسترسی به همین برگه‌ی email address که در شکل فوق ملاحظه می‌کنید از طریق برنامه نویسی) که خلاصه آن تابع زیر است:
جهت استفاده از آن ابتدا باید یک ارجاع به کتابخانه COM ایی به نام Microsoft Outlook Object Library اضافه شود.

using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Outlook;

namespace GAL
{
//add a reference to Microsoft Outlook 12.0 Object Library
class COutLook
{
public struct User
{
public string Name;
public string Email;
}

public static List<User> ExchangeServerEmailAddresses(string userName)
{
List<User> res = new List<User>();
//Create Outlook application
Application outlookApp = new Application();
//Get Mapi NameSpace and Logon
NameSpace ns = outlookApp.GetNamespace("MAPI");
ns.Logon(userName, Missing.Value, false, true);

//Get Global Address List
AddressLists addressLists = ns.AddressLists;
AddressList globalAddressList = addressLists["Global Address List"];
AddressEntries entries = globalAddressList.AddressEntries;
foreach (AddressEntry entry in entries)
{
ExchangeUser user = entry.GetExchangeUser();
if (user != null && user.PrimarySmtpAddress != null && entry.Name != null)
res.Add(new User
{
Name = entry.Name,
Email = user.PrimarySmtpAddress
});
}

ns.Logoff();

// Clean up.
outlookApp = null;
ns = null;
addressLists = null;
globalAddressList = null;
entries = null;

return res;
}
}
}
و نحوه استفاده از آن هم به صورت زیر می‌تواند باشد:

List<COutLook.User> data = COutLook.ExchangeServerEmailAddresses("nasiri");
foreach (var list in data)
{
//....
}
در اینجا Nasiri نام کاربری شخص در دومین است (کاربر جاری لاگین کرده در سیستم).
تنها نکته‌ی مهم این کد، مهیا نبودن فیلد ایمیل در شیء AdderssEntry است که باید از طریق متد GetExchangeUser آن اقدام شود.


مطالب دوره‌ها
آشنایی با نحوه ایجاد یک IoC Container
قبل از اینکه وارد بحث استفاده از کتابخانه‌های بسیار غنی IoC Container موجود شویم، بهتر است یک نمونه ساده آن‌ها را طراحی کنیم تا بهتر بتوان با عملکرد و ساختار درونی آن‌ها آشنا شد.


IoC Container چیست؟

IoC Container، فریم ورکی است برای انجام تزریق وابستگی‌ها. در این فریم ورک امکان تنظیم اولیه وابستگی‌های سیستم وجود دارد. برای مثال زمانیکه برنامه از یک IoC Container، نوع اینترفیس خاصی را درخواست می‌کند، این فریم ورک با توجه به تنظیمات اولیه‌اش، کلاسی مشخص را بازگشت خواهد داد.
IoC Containerهای قدیمی‌تر، برای انجام تنظیمات اولیه خود از فایل‌های کانفیگ استفاده می‌کردند. نمونه‌های جدیدتر آن‌ها از روش‌های Fluent interfaces برای مشخص سازی تنظیمات خود بهره می‌برند.

زمانیکه از یک IOC Container در کدهای خود استفاده می‌کنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست می‌کند. عموما اینکار با درخواست یک اینترفیس صورت می‌گیرد؛ هرچند محدودیتی نیز نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
در این بین زنجیره‌ی وابستگی‌های مورد نیاز نیز وهله سازی خواهند شد. برای مثال اگر وابستگی اول به وابستگی دوم برای وهله سازی نیاز دارد، کار وهله سازی وابستگی‌های وابستگی دوم نیز به صورت خودکار انجام خواهند شد. (این موردی است که بسیاری از تازه واردان به این بحث تا یکبار آن‌را امتحان نکنند باور نخواهند کرد!)
ج) سپس کد فراخوان وهله دریافتی را مورد پردازش قرار داده و سپس شروع به استفاده از متدها و خواص آن خواهد نمود.


در تصویر فوق محل قرارگیری یک IOC Container را مشاهده می‌کنید. یک IOC Container در مورد تمام وابستگی‌های مورد نیاز، اطلاعات لازم را دارد. همچنین این فریم ورک در مورد کلاسی که قرار است از وابستگی‌های سیستم استفاده نماید نیز مطلع است؛ به این ترتیب می‌تواند به صورت خودکار در زمان وهله سازی آن، نوع‌های وابستگی‌های مورد نیاز آن‌را در اختیارش قرار دهد.
برای مثال در اینجا MyClass، وابستگی مشخص شده در سازنده خود را به نام IDependency از IOC Container درخواست می‌کند. سپس این IOC Container بر اساس تنظیمات اولیه خود، یکی از وابستگی‌های A یا B را بازگشت خواهد داد.


آغاز به کار ساخت یک IOC Container نمونه

در ابتدا کدهای آغازین مثال بحث جاری را در نظر بگیرید:
using System;

namespace DI01
{
    public interface ICreditCard
    {
        string Charge();
    }

    public class Visa : ICreditCard
    {
        public string Charge()
        {
            return "Charging with the Visa!";
        }
    }

    public class MasterCard : ICreditCard
    {
        public string Charge()
        {
            return "Swiping the MasterCard!";
        }
    }

    public class Shopper
    {
        private readonly ICreditCard creditCard;

        public Shopper(ICreditCard creditCard)
        {
            this.creditCard = creditCard;
        }

        public void Charge()
        {
            var chargeMessage = creditCard.Charge();
            Console.WriteLine(chargeMessage);
        }
    }    
}
در اینجا وابستگی‌های کلاس خریدار از طریق سازنده آن که متداول‌ترین روش تزریق وابستگی‌ها است، در اختیار آن قرار خواهد گرفت. یک اینترفیس کردیت کارت تعریف شده‌است به همراه دو پیاده سازی نمونه آن مانند مسترکارت و ویزا کارت. ساده‌ترین نوع فراخوانی آن نیز می‌تواند مانند کدهای ذیل باشد (تزریق وابستگی‌های دستی):
 var shopper = new Shopper(new Visa());
shopper.Charge();
در ادامه قصد داریم این فراخوانی‌ها را اندکی هوشمندتر کنیم تا بتوان بر اساس تنظیمات برنامه، کار تزریق وابستگی‌ها صورت گیرد و به سادگی بتوان اینترفیس‌های متفاوتی را در اینجا درخواست و مورد استفاده قرار داد. اینجا است که به اولین IoC Container خود خواهیم رسید:
using System;
using System.Collections.Generic;
using System.Linq;

namespace DI01
{
    public class Resolver
    {
        //کار ذخیره سازی و نگاشت از یک نوع به نوعی دیگر در اینجا توسط این دیکشنری انجام خواهد شد
        private Dictionary<Type, Type> dependencyMap = new Dictionary<Type, Type>();

        /// <summary>
        /// یک نوع خاص از آن درخواست شده و سپس بر اساس تنظیمات برنامه، کار وهله سازی
        /// نمونه معادل آن صورت خواهد گرفت
        /// </summary>
        public T Resolve<T>()
        {
            return (T)Resolve(typeof(T));
        }

        private object Resolve(Type typeToResolve)
        {
            Type resolvedType;

            // ابتدا بررسی می‌شود که آیا در تنظیمات برنامه نگاشت متناظری برای نوع درخواستی وجود دارد؟
            if (!dependencyMap.TryGetValue(typeToResolve, out resolvedType))
            {
                //اگر خیر، کار متوقف خواهد شد
                throw new Exception(string.Format("Could not resolve type {0}", typeToResolve.FullName));
            }

            var firstConstructor = resolvedType.GetConstructors().First();
            var constructorParameters = firstConstructor.GetParameters();
            // در ادامه اگر این نوع، دارای سازنده‌ی بدون پارامتری است
            // بلافاصله وهله سازی خواهد شد
            if (!constructorParameters.Any())
                return Activator.CreateInstance(resolvedType);


            var parameters = new List<object>();
            foreach (var parameterToResolve in constructorParameters)
            {
                // در اینجا یک فراخوانی بازگشتی صورت گرفته است برای وهله سازی
                // خودکار پارامترهای مختلف سازنده یک کلاس
                parameters.Add(Resolve(parameterToResolve.ParameterType));
            }            
            return firstConstructor.Invoke(parameters.ToArray());
        }

        public void Register<TFrom, TTo>()
        {
            dependencyMap.Add(typeof(TFrom), typeof(TTo));
        }
    }
}
در اینجا کدهای کلاس Resolver یا همان IoC Container ابتدایی بحث را مشاهده می‌کنید. توضیحات قسمت‌های مختلف آن به صورت کامنت ارائه شده‌اند.
 var resolver = new Resolver();
//تنظیمات اولیه
resolver.Register<Shopper, Shopper>();
resolver.Register<ICreditCard, Visa>();
//تزریق وابستگی‌ها و وهله سازی
var shopper = resolver.Resolve<Shopper>();
shopper.Charge();
در ادامه نحوه استفاده از IoC Container ایجاد شده را مشاهده می‌کنید.
ابتدا کار تعاریف نگاشت‌های اولیه انجام می‌شود. در این صورت زمانیکه متد Resolve فراخوانی می‌گردد، نوع درخواستی آن به همراه سازنده دارای آرگومانی از نوع ICreditCard وهله سازی شده و بازگشت داده خواهد شد. سپس با در دست داشتن یک وهله آماده، متد Charge آن‌را فراخوانی خواهیم کرد.


بررسی نحوه استفاده از Microsoft Unity به عنوان یک IoC Container

Unity چیست؟

Unity یک فریم ورک IoC Container تهیه شده توسط مایکروسافت می‌باشد که آن‌را به عنوان جزئی از Enterprise Library خود قرار داده است. بنابراین برای دریافت آن یا می‌توان کل مجموعه Enterprise Library را دریافت کرد و یا به صورت مجزا به عنوان یک بسته نیوگت نیز قابل تهیه است.
برای این منظور در خط فرمان پاورشل نیوگت در VS.NET دستور ذیل را اجرا کنید:
 PM> Install-Package Unity

پیاده سازی مثال خریدار توسط Unity

همان مثال قسمت قبل را درنظر بگیرید. قصد داریم اینبار بجای IoC Container دست سازی که تهیه شد، پیاده سازی آن‌را به کمک MS Unity انجام دهیم.
using Microsoft.Practices.Unity;

namespace DI02
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();

            container.RegisterType<ICreditCard, MasterCard>();

            var shopper = container.Resolve<Shopper>();
            shopper.Charge();
        }
    }
}
همانطور که ملاحظه می‌کنید، API آن بسیار شبیه به کلاس دست سازی است که در قسمت قبل تهیه کردیم.
مطابق کدهای فوق، ابتدا تنظیمات IoC Container انجام شده است. به آن اعلام کرده‌ایم که در صورت نیاز به ICreditCard، نوع MasterCard را یافته و وهله سازی کن. با این تفاوت که Unity هوشمند‌تر بوده و سطر مربوط به ثبت کلاس Shoper ایی را که در قسمت قبل انجام دادیم، در اینجا حذف شده است.
سپس به این IoC Container اعلام کرده‌ایم که نیاز به یک وهله از کلاس خریدار داریم. در اینجا Unity کار وهله سازی‌های خودکار وابستگی‌ها و تزریق آن‌ها را در سازنده کلاس خریدار انجام داده و نهایتا یک وهله قابل استفاده را در اختیار ادامه برنامه قرار خواهد داد.

یک نکته:
به صورت پیش فرض کار تزریق وابستگی‌ها در سازنده کلاس‌ها به صورت خودکار انجام می‌شود. اگر نیاز به Setter injection و مقدار دهی خواص کلاس وجود داشت می‌توان به نحو ذیل عمل کرد:
 container.RegisterType<ICreditCard, MasterCard>(new InjectionProperty("propertyName", 5));
نام خاصیت و مقدار مورد نظر به عنوان پارامتر متد RegisterType باید تعریف شوند.


مدیریت طول عمر اشیاء در Unity

توسط یک IoC Container می‌توان یک وهله معمولی از شیءایی را درخواست کرد و یا حتی طول عمر این وهله را به صورت Singleton معرفی نمود (یک وهله در طول عمر کل برنامه). در Unity اگر تنظیم خاصی اعمال نشود، هربار که متد Resolve فراخوانی می‌گردد، یک وهله جدید را در اختیار ما قرار خواهد داد. اما اگر پارامتر متد RegisterType را با وهله‌ای از ContainerControlledLifetimeManager مقدار دهی کنیم:
 container.RegisterType<ICreditCard, MasterCard>(new ContainerControlledLifetimeManager());
از این پس با هربار فراخوانی متد Resolve، در صورت نیاز به وابستگی از نوع ICreditCard، تنها یک وهله مشترک از MasterCard ارائه خواهد شد.
حالت پیش فرض مورد استفاده، بدون ذکر پارامتر متد RegisterType، مقدار TransientLifetimeManager می‌باشد.
مطالب
آموزش Prism #3
در پست‌های قبلی با Prism و روش استفاده از آن آشنا شدیم (قسمت اول) و (قسمت دوم). در این پست با استفاده از Mef قصد ایجاد یک پروژه Silverlight رو به صورت ماژولار داریم. مثال پیاده سازی شده در پست قبلی را در این پست به صورت دیگر پیاده سازی خواهیم کرد.
تفاوت‌های پیاده سازی مثال پست قبلی با این پست:
  • در مثال قبل پروژه به صورت Desktop و با WPF پیاده سازی شده بود ولی در این مثال با Silverlight می‌باشد؛
  • در مثال قبل از UnityBootstrapper استفاده شده بود ولی در این مثال از MefBootstrapper؛
  • در مثال قبل هر View در یک ماژول قرار داشت ولی در این مثال هر دو View را در یک ماژول قرار دادم؛
  • در مثال قبل از Prism Libary 2.x استفاده شده بود ولی در این مثال از PrismLibrary 4.x؛
  • و...

نکته : برای فهم بهتر مفاهیم، آشنایی اولیه با MEF و مفاهیمی نظیر Export و Import و AggregateCatalog و AssemblyCatalog نیاز است. در صورتی که با این مطالب آشنایی ندارید می‌توانید از (^) شروع کنید.


برای شروع یک پروژه Silverlight ایجاد کنید. بعد از اضافه شدن دو پروژه Silverlight و Web، یک Silverlight Class Library جدید بسازید.
ابتدا یک Page ایجاد کنید و کد‌های زیر را در آن کپی کنید.
<UserControl 
             x:Class="Module1.Module1View1"
             xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" FlowDirection="RightToLeft" FontFamily="Tahoma">
    <StackPanel>
        <sdk:DataGrid Height="100">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn Header="کد" Width="50"  />
                <sdk:DataGridTextColumn Header="عنوان" Width="200" />
                <sdk:DataGridTextColumn Header="نویسنده" Width="150"  />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <Button x:Name="NextViewButton"
                Width="150"
                Height="25"
                Foreground="Red"
                Background="Blue"
                Content="لیست طبقه بندی ها" />
    </StackPanel>
</UserControl>
بر روی Page مربوطه راست کلیک کنید و گزینه ViewCode را انتخاب کنید و کد‌های زیر را در آن کپی کنید.
 [Export(typeof(Module1View1))]
    public partial class Module1View1 : UserControl
    {
        [Import]
        public IRegionManager TheRegionManager { private get; set; }

        public Module1View1()
        {
            InitializeComponent();

            NextViewButton.Click += NextViewButton_Click;
        }

        void NextViewButton_Click(object sender, RoutedEventArgs e)
        {
            TheRegionManager.RequestNavigate
            (
                "MyRegion1",
                new Uri("Module1View2", UriKind.Relative),
                a => { }
            );
        }
    }
ابتدا خود این View باید حتما Export شود. در رویداد کلیک با استفاده از متد RequestNavigate می‌توانیم به View مورد نظر برای نمایش در Shell اشاره کنیم و این View در Region نمایش داده می‌شود. به دلیل اینکه در این کلاس به RegionManager نیاز داریم از ImportAttribute استفاده کردیم. این بدین معنی است که کلاس Module1View1 وابستگی مستقیم به IRegionManager دارد.

حال یک Page دیگر برای طبقه بندی کتاب‌ها ایجاد کنید و کدهای زیر را در آن کپی کنید.
<UserControl 
             xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"  x:Class="Module1.Module1View2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" FlowDirection="RightToLeft" FontFamily="Tahoma">
    <StackPanel>
        <sdk:DataGrid Height="100">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn Header="کد" Width="150"/>
                <sdk:DataGridTextColumn Header="عنوان" Width="150"/>                
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <Button x:Name="NextViewButton"
                Width="150"
                Height="25"
                Foreground="Green"
                Background="Yellow"
                Content="لیست کتاب ها" />
    </StackPanel>
</UserControl>
در Code Behind این Page نیز کد‌های زیر را قرار دهید.
using Microsoft.Practices.Prism.Regions;
using System;
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;

namespace Module1
{
    [Export]
    public partial class Module1View2 : UserControl
    {
        IRegion _region1;

        [ImportingConstructor]
        public Module1View2( [Import] IRegionManager regionManager )
        {
            InitializeComponent();
         
            ViewModel viewModel = new ViewModel();
            DataContext = viewModel;

            viewModel.ShouldNavigateFromCurrentViewEvent += () => { return true; };

            _region1 = regionManager.Regions["MyRegion1"];

            NextViewButton.Click += NextViewButton_Click;
        }

        void NextViewButton_Click( object sender, RoutedEventArgs e )
        {
            _region1.RequestNavigate
            (
                new Uri( "Module1View1", UriKind.Relative ),
                a => { }
            );
        }
    }
}
در این ماژول برای اینکه بتوانیم حالت گردشی در فراخوانی ماژول‌ها را داشته باشیم ابتدا DataContext این کلاس را برابر با ViewModel ساخته شده قرار دادیم. با استفاده از رویداد ShoudlNavigateFromCurrentViewEvent که در کلاس ViewModel وجود دارد تعیین می‌کنیم که آیا باید از این View به View قبلی برگشت داشته باشیم یا نه. در صورتی که مقدار false برگشت داده شود خواهید دید که امکان فراخوانی View1 از View2 امکان پذیر نیست. در رویداد کلیک نیز همانند Page قبلی با استفاده از RegionManager و متد RequestNavigate به View مورد نظر راهبری کرده ایم.
نکته: اگر یک کلاس، سازنده با پارامتر داشته باشد باید با استفاده از ImportingConstructor حتما سازنده مورد نظر را هنگام وهله سازی مشخص کنیم در غیر این صورت با Exception مواجه خواهید شد.
حال قصد ایجاد کلاس ViewModel بالا را داریم:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using Microsoft.Practices.Prism.Regions;

namespace Module1
{
    public class ViewModel : IConfirmNavigationRequest
    {       
        public event Func<bool> ShouldNavigateFromCurrentViewEvent;      

        public bool IsNavigationTarget( NavigationContext navigationContext )
        {
            return true;
        }

        public void OnNavigatedTo( NavigationContext navigationContext )
        {

        }

        public void OnNavigatedFrom( NavigationContext navigationContext )
        {

        }         

        public void ConfirmNavigationRequest( NavigationContext navigationContext, Action<bool> continuationCallback )
        {
            bool shouldNavigateFromCurrentViewFlag = false;

            if ( ShouldNavigateFromCurrentViewEvent != null )
                shouldNavigateFromCurrentViewFlag = ShouldNavigateFromCurrentViewEvent();

            continuationCallback( shouldNavigateFromCurrentViewFlag );
        }    
    }
}
توضیح متد‌های بالا:
  • IsNavigateTarget : برای تعیین اینکه آیا کلاس پیاده سازی کننده اینترفیس، می‌تواند عملیات راهبری را مدیریت کند یا نه.
  • OnNavigateTo : زمانی عملیات راهبری وارد View شود(بهتره بگم View مورد نظر در Region صفحه لود شود) این متد فراخوانی می‌شود.
  • OnNavigateFrom : زمانی که راهبری از این View خارج می‌شود (View از حالت لود خارج می‌شود) این متد فراخوانی خواهد شد.
  • ConfirmNavigationRequest : برای تایید عملیات راهبری توسط کلاس پیاده سازی کننده اینترفیس استفاده می‌شود. 
حال یک کلاس برای پیاده سازی و مدیریت ماژول می‌سازیم.
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using System.ComponentModel.Composition;

namespace Module1
{
    [ModuleExport(typeof(Module1Impl))]
    public class Module1Impl : IModule
    {       
        [Import]
        public IRegionManager TheRegionManager { private get; set; }     

        public void Initialize()
        {
            TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View1));

            TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View2));
        }
    }
}
همان طور که مشاهده می‌کنید از ModuleExportAttribute برای شناسایی ماژول توسط MefBootstrapper استفاده کردیم و نوع آن را Module1Imp1 قرار دادیم.  ImportAttribute استفاده شده در این کلاس و خاصیت TheRegionManager برای این است که در هنگام ساخت Instance از این کلاس IRegionManager موجود در Container باید در اختیار این کلاس قرار گیرد(نشان دهنده وابستگی مستقیم این کلاس با IRegionManager است). روش دیگر این است که در سازنده این کلاس هم این اینترفیس را تزریق کنیم.
در متد Initialize برای RegionManager دو View ساخته شده را رجیستر کردیم. این کار باید به تعداد View‌های موجود در ماژول انجام شود.
Shell
در پروژه اصلی بک Page به نام Shell ایجاد کنید و کد‌های زیر را در آن کپی کنید.
<UserControl x:Class="NavigationViaViewModel.Shell"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://www.codeplex.com/prism" FlowDirection="RightToLeft" FontFamily="Tahoma">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <TextBlock Text="لیست کتاب‌ها به همراه طبقه بندی آن ها"
                   FontSize="19"
                   Foreground="Black"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Top" />
        <ContentControl HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        prism:RegionManager.RegionName="MyRegion1" />
    </Grid>
</UserControl>
همانند مثال قبلی یک ContentControl داریم و به وسیله RegionName که یک AttachedProperty است یک Region به نام MyRegion1 ایجاد کردیم. تمام ماژول‌های این مثال در این محدوده نمایش داده خواهند شد.
Bootstrapper
حال نیاز به یک Bootstrapper داریم. برای این کار یک کلاس به نام TheBootstrapper بسازید:
using Microsoft.Practices.Prism.MefExtensions;
using Microsoft.Practices.Prism.Modularity;
using System.ComponentModel.Composition.Hosting;
using System.Windows;

namespace NavigationViaViewModel
{
    public class TheBootstrapper : MefBootstrapper
    {
        protected override void InitializeShell()
        {
            base.InitializeShell();

            Application.Current.RootVisual = (UIElement)Shell;
        }

        protected override DependencyObject CreateShell()
        {
            return Container.GetExportedValue<Shell>();
        }

        protected override void ConfigureAggregateCatalog()
        {
            base.ConfigureAggregateCatalog();        
            AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            ModuleCatalog moduleCatalog = new ModuleCatalog();
    
            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    InitializationMode = InitializationMode.WhenAvailable,
                    Ref = "Module1.xap",
                    ModuleName = "Module1Impl",
                    ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

            return moduleCatalog;
        }
    }
}
متد CreateShell اولین متد در این کلاس است که اجرا خواهد شد. بعد از متد CreateShell، متد InitializeShell اجرا خواهد شد. خاصیت Shell دقیقا به مقدار برگشتی متد CreateShell اشاره خواهد کرد. در متد InitializeShell  مقدار خاصیت Shell به RootVisual این پروژه اشاره می‌کند(مانند MainWindow در کلاس Application پروژه‌های WPF).
متد ConfigureAggregateCatalog برای مدیریت کاتالوگ‌ها و ماژول‌ها که هر کدام در یک اسمبلی جدا وجود خواهند شد استفاده می‌شود. در این متد من از AssemblyCatalog استفاده کردم. AssemblyCatalog  تمام کلاس هایی که ExportAttribute را به همراه دارند شناسایی می‌کند و آن‌ها را در Container نگهداری خواهد کرد(^). مانند یک ServiceLocator در Microsoft unity Service Locator(^) .
متد آخر به نام CreateModuleCatalog است و باید در آن تمام ماژول‌های برنامه را به کلاس ModuleCatalog اضافه کنیم. در مثال پست قبلی به دلیل استفاده از UnityBootstrapper باید این کار را از طریق BuildEvent ‌ها مدیریت می‌کردیم ولی در این جا Mef به راحتی این کار را انجام خواهد داد.
تغییرات زیر را در فایل App.Xaml قرار دهید و پروژه را اجرا کنید.
public partial class App : Application
 {
        public App()
        {
            this.Startup += this.Application_Startup;                 
            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
           var bootstrapper = new TheBootstrapper();
            bootstrapper.Run(); 
        }
}
با کلیک بر روی ماژول عملیات راهبری برای ماژول انجام خواهد شد.

دریافت سورس پروژه
ادامه دارد..