مطالب
تست کردن متدهای یک Controller به کمک PowerShell

ابزارهای زیادی جهت تست کردن API ‌های برنامه‌های وب موجود است. یکی از معروفترین آنها  Fiddler است که ابزاری مستقل جهت دیباگ تحت پروتکل HTTP می‌باشد. یکی دیگر از این نرم افزارها Swashbuckle است که از Nuget قابل دریافت است و صفحه‌ای را به برنامه اضافه می‌کند که به اختصار API ‌های برنامه را نمایش داده و امکان اجرای آنها را فراهم میکند.

اما ساده‌ترین راه جهت کردن تست کردن API ‌های یک برنامه، استفاده از PowerShell است که عمل ایجاد درخواست‌های HTTP را به راحتی از طریق Command line فراهم میکند و به شما اجازه میدهد بدون وارد شدن به جزئیات، بر روی نتیجه API مورد نظر تمرکز کنید. PowerShell ابتدا فقط برای محیط ویندوز طراحی شده بود ولی در حال حاضر قابلیت اجرای تحت Linux و Mac را نیز دارد.

در این بخش به شما نشان خواهم داد که چگونه متدهای یک Controller را با استفاده از PowerShell تست نمایید.

بدین منظور ابتدا کلاسی را به نام Reservation با مشخصات زیر، در یک پروژه ASP.NET Core Web Application  ایجاد نمایید.
public class Reservation
{
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
}
و سپس Interface زیر را جهت انجام عملیات CRUD اضافه نمائید:
public interface IRepository
{
        IEnumerable<Reservation> Reservations { get; }
        Reservation this[int id] { get; }
        Reservation AddReservation(Reservation reservation);
        Reservation UpdateReservation(Reservation reservation);
        void DeleteReservation(int id);
}
و کلاسی که Interface فوق را پیاده سازی خواهد کرد:
public class MemoryRepository : IRepository
 {
        private Dictionary<int, Reservation> items;
        public MemoryRepository()
        {
            items = new Dictionary<int, Reservation>();
            new List<Reservation> {
                new Reservation { ClientName = "Alice", Location = "Board Room" },
                new Reservation { ClientName = "Bob", Location = "Lecture Hall" },
                new Reservation { ClientName = "Joe", Location = "Meeting Room 1" }
            }.ForEach(r => AddReservation(r));
        }

        public Reservation this[int id] => items.ContainsKey(id) ? items[id] : null;
        public IEnumerable<Reservation> Reservations => items.Values;

        public Reservation AddReservation(Reservation reservation)
        {
            if (reservation.ReservationId == 0)
            {
                int key = items.Count;
                while (items.ContainsKey(key)) { key++; };
                reservation.ReservationId = key;
            }
            items[reservation.ReservationId] = reservation;
            return reservation;
        }

        public void DeleteReservation(int id) => items.Remove(id);
        public Reservation UpdateReservation(Reservation reservation) => AddReservation(reservation);
}
در کلاس فوق به جهت سهولت در کار، از یک لیست، برای نگهداری تعدادی رکورد اطلاعاتی جهت نمایش استفاده شده است.

سپس کنترلری را به نام ReservationController به پروژه اضافه کنید که شامل متدهای زیر باشد:
[Route("api/[controller]")]
public class ReservationController : Controller
    {
        private IRepository repository;
        public ReservationController(IRepository repo) => repository = repo;

        [HttpGet]
        public IEnumerable<Reservation> Get() => repository.Reservations;

        [HttpGet("{id}")]
        public Reservation Get(int id) => repository[id];

        [HttpPost]
        public Reservation Post([FromBody] Reservation res) =>
                    repository.AddReservation(new Reservation
                    {
                        ClientName = res.ClientName,
                        Location = res.Location
                    });
        [HttpPut]
        public Reservation Put([FromBody] Reservation res) => repository.UpdateReservation(res);

        [HttpPatch("{id}")]
        public StatusCodeResult Patch(int id, [FromBody] JsonPatchDocument<Reservation> patch)
        {
            Reservation res = Get(id);
            if (res != null)
            {
                patch.ApplyTo(res);
                return Ok();
            }
            return NotFound();
        }

        [HttpDelete("{id}")]
        public void Delete(int id) => repository.DeleteReservation(id);
}
حالا جهت تست صحت این API‌ها توسط PowerShell لازم است ابتدا برنامه را اجرا کرده و منتظر بمانید تا صفحه Home ظاهر شود. پس از آن لازم است یک پنجره PowerShell را از طریق کلیک راست در یکی از فولدرهای نمایش داده شده در FileExplorer باز کنید.

تست کردن درخواست از نوع GET

حالا دستور زیر را در پنجره PowerShell وارد کنید:
 Invoke-RestMethod http://localhost:7000/api/reservation -Method GET
لازم است شماره پورت را مطابق با پورتی که برنامه شما بر روی آن اجرا شده تغییر دهید. این فرمان به کمک دستور Invoke-RestMethod درخواستی از نوع GET را به api/reservation ارسال می‌کند و نتیجه را پس از تجزیه و تحلیل و فرمت بندی، جهت سهولت در خوانده شدن، به شکل زیر نمایش میدهد:

location  clientName  reservationId 
Board Room 
Alice 
0                  
Lecture Hall 
Bob
1
Meeting Room 1 
Joe
2
فرمت اطلاعات دریافت شده از درخواست GET، اشیایی از نوع کلاس Reservation است و به فرمت Json می‌باشد؛ ولی Invoke-RestMethod آنرا به صورت جدول نمایش میدهد.
حال جهت نمایش فقط یک رکورد از اطلاعات فوق، دستور زیر را وارد نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation/1 -Method GET
این فرمان درخواستی جهت دریافت اطلاعات شیء Reservation است که کد داخلی آن برابر 1 است. پاسخ این درخواست به شکل زیر نمایش داده خواهد شد:

location  clientName   reservationId   
Lecture Hall  
        Bob
1                  


تست درخواست از نوع Post

تمامی متدهای ارائه شده توسط یک API را می‌توان به کمک PowerShell تست کرد. اگرچه در این حالت فرمت بعضی از درخواستهای ارسالی ممکن است کمی به هم ریخته به نظر برسد. در اینجا درخواستی جهت اضافه کردن یک رکورد را به لیست Reservation ارسال می‌کنیم و نتیجه را در پاسخ ارسال شده از سمت سرور خواهیم دید:

Invoke-RestMethod http://localhost:7000/api/reservation -Method POST -Body
(@{clientName="Anne"; location="Meeting Room 4"} | ConvertTo-Json) -ContentType "application/json"
این دستور با استفاده از آرگومان Body- محتویات درخواست را که با فرمت Json است تعیین می‌کند. آرگومان Content-Type- هم نوع محتویات درخواستی را در هدر مشخص میکند. دستور فوق در صورت موفقیت نتیجه زیر را نمایش خواهد داد:

location  clientName   reservationId   
 Meeting Room 4
Anne      
1                
           
دستور Post ارسالی، با استفاده از دو فیلد clientName و location، یک رکورد جدید از نوع Reservation را به لیست موجود اضافه کرده و نتیجه را در قالب Json به سمت کلاینت برگشت خواهد داد که شامل ReservationId می‌باشد که به رکورد جدید اختصاص داده شده‌است. ممکن است چنین به نظر برسد که نتیجه برگشت داده شده، همان درخواست ارسالی است که به شکل جدول فوق نمایش داده می‌شود؛ ولی چنین نیست. جهت مشاهده تاثیر درخواست POST ارسالی بر روی منبع داده، کافی است یک بار دیگر دستور زیر را در command line وارد کنید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
 Board Room           Alice
0              
 Lecture Hall  Bob 1
 Meeting Room 1  Joe 2
 Meeting Room 4  Anne 3
داده‌هایی که به سمت کلاینت ارسال شده، نشان دهنده اضافه شدن رکورد جدیدی به منبع داده ماست.

تست درخواست از نوع PUT

درخواست از نوع PUT جهت جایگزینی داده‌ای موجود در لیست، با داده‌ی جدید است. بدین منظور کد داخلی شیء مورد نظر (در اینجا ReservationId) باید در URL گنجانده شود و مقادیر فیلدهای کلاس، در متن درخواست. مثالی جهت درخواست اصلاح داده موجود از طریق فرمان PowerShell :

Invoke-RestMethod http://localhost:7000/api/reservation -Method PUT -Body
(@{reservationId="1"; clientName="Bob"; location="Media Room"} | ConvertTo-Json)
-ContentType "application/json"
درخواست فوق، فیلد Location رکوردی با کد داخلی 1 را به مقدار جدیدی تغییر خواهد داد و نتیجه تغییر انجام شده را در پاسخ ارسال خواهد کرد:

location  clientName   reservationId   
Media Room
Bob        
1                

و باز با ارسال درخواست زیر می‌توان نتیجه کلی را مشاهده کرد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId   
Board Room
Alice  
0  
 Media Room
 Bob  1
 Meeting Room 1
Joe 2
 Meeting Room 4
Anne
3


استفاده از دستور PATCH

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

ASP.NET Core MVC از استاندارد Json PATCH پشتیبانی می‌کند و این کار اجازه میدهد تا بتوان تغییرات را بطور یکنواختی انجام داد. جهت مشاهده جزئیات این دستور می‌توانید به این لینک مراجعه کنید. اما برای مثال فرض کنید می‌خواهید چنین درخواستی را که به فرمت Json است از طریق HTTP PATCH به سمت سرور ارسال کنید:

[
  { "op": "replace", "path": "clientName", "value": "Bob"},
  { "op": "replace", "path": "location", "value": "Lecture Hall"}
]

دربسیاری از مواقع فقط از دستور replace درخواست PATCH استفاده می‌شود و کد داخلی رکورد مورد نظر، جهت تغییر در Url درخواست ارسالی، فرستاده خواهد شد. ASP.NET Core MVC به طور اتوماتیک این دستور را پردازش کرده و آنرا به صورت آبجکتی از نوع <JsonPatchDocument<T به اکشن کنترلر قید شده، پاس خواهد داد که در اینجا نوع T، از نوع کلاس مورد نظر ما می‌باشد. فرمت دستور ارسالی از طریق Powershell به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method PATCH -Body (@
{ op="replace"; path="clientName"; value="Bob"},@{ op="replace"; path="location";
value="Lecture Hall"} | ConvertTo-Json) -ContentType "application/json"
دستور فوق درخواست تغییر دو فیلد clientname و location، با کد داخلی 2 می‌باشد.
جهت مشاهده تغییر ایجاد شده، دستور زیر را مجددا اجرا نمایید:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

location  clientName   reservationId  
Board Room
Alice  
0            
Media Room
Bob
 1
Lecture Hall
 Bob 2
Meeting Room 4
 Anne 3


استفاده از دستور Delete

استفاده از دستور Delete هم به شکل زیر خواهد بود:

Invoke-RestMethod http://localhost:7000/api/reservation/2 -Method DELETE
توضیح اینکه در این حالت پاسخی از سمت سرور ارسال نخواهد شد. اما جهت مشاهده تاثیر این دستور بر روی اطلاعات موجود می‌توان مجددا دستور زیر را جهت نمایش داده‌ها بکار برد:
Invoke-RestMethod http://localhost:7000/api/reservation -Method GET

کدهای این مقاله به پیوست موجود است: ApiControllers.zip
مطالب
آشنایی با کلاس JavaScriptSerializer

برای استفاده از jQuery Ajax یکی از روش‌های ارسال دیتا به برنامه، تبدیل داده‌ها به فرمت JSON می‌باشد. برای داده‌های ساده، تشخیص این فرمت ساده است. مثلا اگر امضای تابع وب سرویس اجکس ما به صورت زیر باشد:
public static bool IsUserAvailable(string username)
اطلاعات جی‌سونی را که قرار است ارسال کنیم، فرمت زیر را باید داشته باشد:
{'username':'value'}
حال اگر آرگومان‌های ما پیچیده‌تر بودند چطور؟ مثلا بجای یک رشته ساده، یک لیست جنریک داشتیم، فرمت ورودی را چگونه باید تشخیص داد؟
برای این منظور در دات نت 3 و نیم، کلاسی جهت انجام اینگونه تبدیلات پیش بینی شده است که شرح مختصر آن به صورت زیر است:
ابتدا باید ارجاعی را به اسمبلی system.web.extensions به برنامه افزود و سپس جهت سهولت کار می‌توان یک extension method از کلاس JavaScriptSerializer مهیا در فضای نام System.Web.Script.Serialization ایجاد کرد:

public static string ToJson(this object data)
{
return new JavaScriptSerializer().Serialize(data);
}
اکنون چند مثال زیر را در نظر بگیرید:
        public static string GetJsonTest0()
{
var data = "a1";
return data.ToJson();
}

public static string GetJsonTest1()
{
var data = new List<string> { "a1", "a2", "a3" };
return data.ToJson();
}

public static string GetJsonTest2()
{

var lst =
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "علی"),
new KeyValuePair<string, object>("Number", 10),
new KeyValuePair<string, object>("Desc", "منابع مورد نیاز")
};

return lst.ToJson();

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

"a1"
["a1","a2","a3"]
[{"Key":"Name","Value":"علی"},{"Key":"Number","Value":10},{"Key":"Desc","Value":"منابع مورد نیاز"}]

این کلاس همچنین قابلیت Deserialize و تبدیل داده‌هایی به فرمت JSON به اشیاء مورد نظر ما را نیز دارا است.

مطالب
شروع به کار با DNTFrameworkCore - قسمت 2 - طراحی موجودیت‌های سیستم
در قسمت قبل، امکانات این زیرساخت را ملاحظه کردیم. در این مطلب و مطالب آینده، روش طراحی بخش‌های مختلف یکسری سیستم فرضی را با استفاده از امکانات مذکور و با جزئیات بیشتر، بررسی خواهیم کرد.
به منظور اعمال خودکار یکسری مفاهیم توسط زیرساخت، نیاز است موجودیت‌های شما قراردادهای مورد نظر را پیاده سازی کرده باشند یا اینکه از موجودیت‌های پایه که آن قراردادها را پیاده سازی کرده‌اند، به عنوان میانبر، از آنها ارث بری کنید. برای دسترسی به این موجودیت‌های پایه و یکسری واسط که به عنوان قراردادهایی در بخش‌های مختلف این زیرساخت استفاده می‌شوند، نیاز است تا ابتدا بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore -Version 1.0.0

مثال اول: یک موجودیت ساده بدون نیاز به مباحث ردیابی تغییرات

public class MeasurementUnit : Entity<int>, IAggregateRoot
{
   public const int MaxTitleLength = 50;
   public const int MaxSymbolLength = 50;

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Symbol { get; set; }
    public byte[] RowVersion { get; set; }
}

‎کلاس جنریک Entity، در برگیرنده یکسری اعضای مشترک بین سایر موجودیت‌های سیستم از جمله Id و TrackingState (به منظور سناریوهای Master-Detail)، می‌باشد. 

‎نکته: در این زیرساخت برای پیاده سازی CrudService برای یک موجودیت خاص، نیاز است تا واسط IAggregateRoot را نیز پیاده سازی کرده باشد. برای پیاده سازی واسط مذکور نیاز است تا خصوصیت RowVersion را به منظور مدیریت Optimistic مباحث همزمانی، به کلاس بالا اضافه کنیم. این موضوع برای موجودیت‌های وابسته به یک Aggregate ضروری نیست، چرا که آنها با AggregateRoot ذخیره خواهند شد و تراکنش جدایی برای ثبت، ویرایش و یا حذف آنها وجود ندارد.

مثال دوم: یک موجودیت به همراه مباحث ردیابی تغییرات ثبت و آخرین ویرایش

public class Blog : TrackableEntity<long>, IAggregateRoot
{
    public const int MaxTitleLength = 50;
    public const int MaxUrlLength = 50;

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Url { get; set; }
    public byte[] RowVersion { get; set; }
}

کلاس جنریک TrackableEntity علاوه بر خصوصیات Id و TrackingState، یکسری خصوصیت دیگر از جمله زمان ثبت، زمان آخرین ویرایش، شناسه کاربر ثبت کننده، شناسه آخرین کاربر ویرایش کننده، اطلاعات مرورگرهای آنها و ... را نیز دارا می‌باشد. این خصوصیات به صورت خودکار توسط زیرساخت مقداردهی خواهند شد.


مثال سوم: یک موجودیت به همراه مباحث ردیابی تغییرات ثبت، آخرین ویرایش و حذف نرم

public class Blog : FullTrackableEntity<long>, IAggregateRoot
{
    public const int MaxTitleLength = 50;
    public const int MaxUrlLength = 50;

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Url { get; set; }
    public byte[] RowVersion { get; set; }
}

کلاس جنریک FullTrackableEntity علاوه بر خصوصیات ذکر شده در مثال دوم، یکسری خصوصیت دیگر از جمله IsDeleted، شناسه کاربر حذف کننده، زمان حذف و ... را نیز دارا می‌باشد. همچنین مباحث فیلتر خودکار رکوردهای حذف شده، به صورت خودکار توسط زیرساخت انجام می‌گیرد که امکان غیرفعال کردن آن در شرایط مورد نیاز نیز وجود دارد.

مثال چهارم: یک موجودیت با پشتیبانی از چند مستاجری

public class Blog : Entity<long>, IAggregateRoot, ITenantEntity
{
    public const int MaxTitleLength = 50;
    public const int MaxUrlLength = 50;
    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Url { get; set; }
    public byte[] RowVersion { get; set; }
    public long TenantId { get; set; }
}

با پیاده سازی واسط ITenantEntity، به صورت خودکار خصوصیت TenantId آن با توجه به اطلاعات مستاجر جاری سیستم مقداردهی خواهد شد و همچنین فیلتر خودکار بر روی رکوردهای مستاجرهای مختلف، توسط زیرساخت انجام می‌شود که این مکانیزم هم قابلیت غیرفعال شدن در شرایط خاص را دارد.

مثال پنجم: یک موجودیت به همراه تعدادی موجودیت جزئی (سناریوهای Master-Detail)

public class Invoice : TrackableEntity<long>, IAggregateRoot
{
    public InvoiceStatus Status { get; set; }
    public decimal TotalNet { get; set; }
    public decimal Total { get; set; }
    public decimal PayableTotal { get; set; }
    public decimal Debit { get; set; }
    public decimal Credit { get; set; }
    public decimal Gratuity { get; set; }
    public byte[] RowVersion { get; set; }

    public ICollection<InvoiceItem> Items { get; set; }
}

public class InvoiceItem : TrackableEntity
{
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Price { get; set; }
    public decimal UnitPriceDiscount { get; set; }

    public long ItemId { get; set; }
    public Item Item { get; set; }
    public long InvoiceId { get; set; }
    public Invoice Invoice { get; set; }
}

همانطور که مشخص می‌باشد، موجودیت وابسته یا همان Detail، نیاز به پیاده سازی IAggregateRoot را نخواهد داشت. همانطور که اشاره شد، تراکنش مجزایی برای این موجودیت‌ها نخواهیم داشت و درون تراکنش AggregateRoot، عملیات CRUD آنها انجام خواهد شد و برای انجام عملیات ویرایش، به همراه Root متناظر با خود، واکشی خواهند شد. این موضوع یکی از نقاط قوت زیرساخت محسوب می‌شود که در مقالات آینده و در قسمت طراحی سرویس‌های متناظر با موجودیت‌های سیستم، با جزئیات بیشتری بررسی خواهد شد.

مثال ششم: یک موجودیت با امکان شماره گذاری خودکار

public class Task : TrackableEntity, IAggregateRoot, INumberedEntity
{
    public const int MaxTitleLength = 256;
    public const int MaxDescriptionLength = 1024;

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Number { get; set; }
    public string Description { get; set; }
    public TaskState State { get; set; } = TaskState.Todo;
    public byte[] RowVersion { get; set; }
}

همانطور که در مطلب «طراحی و پیاده سازی زیرساختی برای تولید خودکار کد منحصر به فرد در زمان ثبت رکورد جدید» ملاحظه کردید، نیاز است تا موجودیت مورد نظر، پیاده ساز واسط INumberedEntity نیز باشد. این واسط دارای خصوصیت رشته‌ای Number می‌باشد و همچنین زیرساخت به صورت خودکار در زمان ثبت، این خصوصیت را برای موجودیت‌هایی از این نوع، با رعایت مباحث همزمانی مقداردهی می‌کند.

مثال هفتم: یک موجودیت با امکان ذخیره سازی اطلاعات اضافی در قالب فیلد JSON

public class Task : TrackableEntity, IAggregateRoot, INumberedEntity, IExtendableEntity
{
    public const int MaxTitleLength = 256;
    public const int MaxDescriptionLength = 1024;

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Number { get; set; }
    public string Description { get; set; }
    public TaskState State { get; set; } = TaskState.Todo;
    public byte[] RowVersion { get; set; }

    public string ExtensionJson { get; set; }
}

با پیاده سازی واسط IExtendableEntity، یکسری متد الحاقی برروی اشیاء موجودیت مورد نظر فعال خواهند شد که امکان مقداردهی یا خواندن این اطلاعات اضافی را خواهید داشت. به عنوان مثال:

var task = new Task();
task.SetExtensionValue("Name","Value");
var value = task.ReadExtensionValue("Name");
//or any complex object as string json

با دو متد الحاقی استفاده شده در بالا، امکان مقداردهی، تغییر و خواندن مقدار خصوصیت‌های اضافی را خواهیم داشت که نیاز است موجودیت مورد نظر در دل خود نگهداری کند ولی ارزش و اهمیت زیادی در Domain ندارند.


مثال هشتم: طراحی یک نوع شمارشی (Enum)

public class OrderStatus : Enumeration
{
    public static OrderStatus Submitted = new OrderStatus(1, nameof(Submitted).ToLowerInvariant());
    public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
    public static OrderStatus StockConfirmed = new OrderStatus(3, nameof(StockConfirmed).ToLowerInvariant());
    public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
    public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
    public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());

    protected OrderStatus()
    {
    }

    public OrderStatus(int id, string name)
        : base(id, name)
    {
    }
}

برای سناریوهایی که صرفا قصد انتخاب یک یا چند (حالت enum flags) مورد از بین یک لیست مشخص و سپس ذخیره سازی آنها را دارید، استفاده از نوع داده enum کفایت می‌کند؛ ولی اگر قصد استفاده از آنها برای flow control را دارید، در این صورت به طراحی شکننده‌ای خواهید رسید که پر شده است از if/else هایی که مقادیر مختلف enum مورد نظر را بررسی می‌کنند. با استفاده از کلاس Enumeration امکان مدل کردن انوع شمارشی که مرتبط هستند با منطق تجاری سیستم را با راه حل شیء گرا خواهید داشت. در این صورت رفتارهای متناظر با هریک از فیلدهای یک نوع شمارشی می‌تواند به عنوان رفتاری در دل خود کپسوله شده باشد و اینبار داده و رفتار کنار هم خواهند بود. 

نکته: برای مطالعه بیشتر می‌توانید به مطالب ^ و ^ مراجعه کنید.

در نهایت می‌توانید برای سناریوهای خاص خودتان از سایر واسط های موجود در زیرساخت، نیز به شکل زیر استفاده کنید:

نیاز به حذف نرم بدون نگهداری اطلاعات ردیابی تغییرات

public interface ISoftDeleteEntity
{
    bool IsDeleted { get; set; }
}

.با پیاده سازی واسط بالا این امکان را خواهید داشت که صرفا از مکانیزم حذف نرم استفاده کنید؛ بدون نیاز به نگهداری سایر اطلاعات

نیاز به مقداردهی خودکار زمان ثبت یک موجودیت خاص 

این امر با پیاده سازی واسط زیر امکان پذیر خواهد بود.

public interface IHasCreationDateTime
{
    DateTimeOffset CreationDateTime { get; set; }
}

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

مطالب
آشنایی با الگوی طراحی Fly Weight
سناریوی زیر را در نظر بگیرید:
فرض کنید از شما خواسته شده است تا یک پردازشگر متن را بنویسید. خوب در این پردازشگر با یک‌سری کاراکتر روبرو هستید که هر کاراکتر احتمالا آبجکتی از نوع کلاس خود می‌باشد؛ برای مثال آبجکت XYZ که آبجکتی از نوع کلاس A هست و برای نمایش کاراکتر A استفاده می‌شود. این آبجکت‌ها دارای دو دسته خصیصه هستند: (مطالعه بیشتر )
  1. خصیصه‌های ثابت: یعنی همه کاراکترهای A دارای یک شکل مشخص هستند. در واقع مشخصات ذاتی آبجکت می‌باشند.
  2. خصیصه‌های پویا: یعنی هر کاراکتر دارای فونت، سایز و رنگ خاص خود است. در واقع خصیصه‌هایی که از یک آبجکت به آبجکت دیگر متفاوت هستند .
خوب احتمالا در ساده‌ترین راه حل، به ازای تک تک کاراکترهایی که کاربر وارد می‌کند، یک آبجکت از نوع کلاس متناسب با آن ساخته می‌شود. ولی بحث مهم این است که با این همه آبجکت که هر یک مصرف خود را از حافظه دارند، می‌خواهید چکار کنید؟ احتمالا به مشکل حافظه برخورد خواهید کرد! پس باید یک سناریوی بهتر ایجاد کرد.
سناریوی پیشنهادی این است که برای هر نوع کاراکتر، یک کلاس داشته باشیم، همانند قبل(یک کلاس برای A یک کلاس برای B و غیره) و یک استخر پر از آبجکت داشته باشیم که آبجکت‌های ایجاد شده در آن ذخیره شوند.
سپس کاربر، کاراکتر A را درخواست می‌کند. ابتدا به این استخر نگاه می‌کنیم. اگر کاراکتر A موجود بود، آن را برمی‌گردانیم و اگر موجود نبود، یک آبجکت از نوع A می‌سازیم، سپس این آبجکت را در استخر ذخیره می‌کنیم و آبجکت را بر می‌گردانیم. در این صورت اگر کاربر دوباره درخواست A را کرد، دیگر نیازی به ساخت آبجکت جدید نیست و از آبجکت قبلی می‌توانیم استفاده نماییم. با این شرایط تکلیف خصایص ایستا مشخص است. ولی مشکل مهم با خصایص پویا این است که می‌توانند بین آبجکت‌ها متفاوت باشند که برای این هم یک متد در کلاس‌ها قرار می‌دهیم تا این خصایص را تنظیم نماید.
به کد زیر دقت نمایید:
    public interface IAlphabet
    {
        void Render(string font);//Define Extrinsic and non-static states for each object
    }

    public class A : IAlphabet
    {
        public void Render(string font) { Console.WriteLine(GetType().Name + " has font of type " + font); }
    }
    public class B : IAlphabet
    {
        public void Render(string font) { Console.WriteLine(GetType().Name + " has font of type " + font); }
    }
از متد Render برای تنظیم نمودن خصایص پویا استفاده خواهد شد. 
سپس در ادامه به یک موتور نیاز داریم که قبل از ساخت آبجکت، استخر را بررسی نماید:
    public class FlyWeightFactory
    {
        private readonly Dictionary<string, IAlphabet> _dictionary = new Dictionary<string, IAlphabet>();
        public int Count { get { return _dictionary.Count; } }
        public IAlphabet GetObject(string name)
        {
            if (!_dictionary.ContainsKey(name))
                switch (name)
                {
                    case "A":
                        _dictionary.Add(name, new A());
                        Console.WriteLine("New object created");
                        break;
                    case "B":
                        _dictionary.Add(name, new B());
                        Console.WriteLine("New object created");
                        break;
                    default:
                        throw new Exception("Factory can not create given object");
                }
            else
                Console.WriteLine("Object reused");
            return _dictionary[name];
        }
    }
در اینجا _dictionaries همان استخر ما می‌باشد که قرار است آبجکت‌ها در آن ذخیره شوند. Count برای نمایش تعداد آبجکت‌های موجود در استخر استفاده می‌شود (حداکثر مقدار آن چقدر خواهد بود؟). GetObject نیز همان موتور اصلی کار است که در آن ابتدای استخر بررسی می‌شود. اگر آبجکت در استخر نبود، یک نمونه‌ی جدید از آن ساخته شده، به استخر اضافه گردیده و برگردانده می‌شود.
لذا برای استفاده‌ی از این کد داریم:
 FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
 IAlphabet alphabet = flyWeightFactory.GetObject(typeof(A).Name);
 alphabet.Render("Arial");
 Console.WriteLine();
 alphabet = flyWeightFactory.GetObject(typeof(B).Name);
 alphabet.Render("Tahoma");
 Console.WriteLine();
 alphabet = flyWeightFactory.GetObject(typeof(A).Name);
 alphabet.Render("Time is New Roman");
 Console.WriteLine();
 alphabet = flyWeightFactory.GetObject(typeof(A).Name);
 alphabet.Render("B Nazanin");
 Console.WriteLine();
 Console.WriteLine("Total new alphabet count:" + flyWeightFactory.Count);
با اجرای این کد خروجی زیر را مشاهده خواهید نمود:


نکته‌ی قابل توجه این است که این الگو بصورت داخلی از الگوی Factory Method استفاده می‌کند. با توجه بیشتر به پیاده سازی Flyweight Factory شباهت هایی بین آن و Singleton Pattern می‌بینیم. کلاس‌هایی از این دست را Multiton می نامند. در Multiton نمونه‌ها بصورت زوج کلیدهایی نگهداری می‌شوند و بر اساس Key دریافت شده نمونه‌ی متناظر بازگردانده می‌شود. همچنین در Singleton تضمین می‌شود که از کلاس مربوطه فقط یک نمونه در کل Application وجود دارد. در Multiton Pattern تضمین می‌شود که برای هر Key تنها یک Instance وجود دارد.  

مطالب
بازسازی کد: جایگزینی متد با شیء (Replace method with method object)
زیبایی متدهای کوچک، در قابل درک بودن آنها نهفته است. هدف نهایی بیشتر روش‌های بازسازی کد، تغییر تکه کدی بزرگ و در هم تنیده، به تکه‌های کوچک قابل درک‌تر است. مهمترین مانع در شکستن تکه کدهای طولانی، وجود متغیرهای موقتی در آن است. چند روش بازسازی کد مطرح شده به طور خاص به این موضوع توجه دارند. 
زمانیکه متد طولانی ای در کد وجود داشته باشد که نتوان (به راحتی) آن را به چند متد کوچکتر تقسیم کرد، معمولا از این روش بازسازی کد استفاده می‌شود. 
در این روش می‌توان کلاسی را تعریف کرد و متد مربوطه را به آن کلاس منتقل کرد. در نتیجه متغیرهای حاضر در متد را می‌توان در کلاس، به فیلدهای اطلاعاتی تبدیل کرد. پس از این تغییر حتی می‌توان فراتر رفت و متد ابتدایی را به راحتی به چند متد معنی دار در کلاس مقصد تبدیل کرد. 
فرض کنید در کلاس زیر متدی نسبتا طولانی برای محاسبه قیمت سفارش وجود دارد.  
public class Order { 
    public decimal Price() { 
        var primaryBasePrice = 10000; 
        var branchDiscountBasePrice = 9000; 
        var taxPercentage = 9; 
        // calculate price 
    } 
}
متدی مانند متد Price معمولا پیچیده‌تر از موردی است که در این مثال آورده شده است. علارغم این، متد ذکر شده با توجه به متغیرهای محلی موقتی ای که دارد و روالی که در آن نیاز به پیاده سازی است، ممکن است به اندازه‌ای به متغیرهای مربوطه وابسته باشد که نتوان آن را به متدهای کوچکتر در همین کلاس تقسیم کرد و یا تقسیم کردن آن در همین کلاس کمک چندانی به ما نکند. در چنین شرایطی بازسازی کد مورد نظر این نوشتار انجام می‌شود و کد، شکلی شبیه به تکه کد زیر را به خود می‌گیرد. 
public class Order 
{ 
    public decimal Price() { 
        return new PriceCalculator(this).Compute(); 
    } 
} 
public class PriceCalculator 
{ 
    private Order orderInstance ;
    public PriceCalculator(Order order) 
    { 
        orderInstance = order;
    } 
    public decimal Compute() 
    { 
        var primaryBasePrice = 10000; 
        var branchDiscountBasePrice = 9000; 
        var taxPercentage = 9; 
        // calculate price 
    } 
}

در بازسازی ذکر شده، کلاسی با نام PriceCalculator برای روال محاسبه قیمت ایجاد شده و متدی با نام Compute در آن ساخته شده است که محاسبه قیمت را انجام می‌دهد. به دلیل این که ممکن است در کد مربوط به محاسبه قیمت، از خصوصیات موجود در کلاس Order استفاده شود، شیء مربوطه نیز به صورت پارامتر به کلاس محاسبه قیمت پاس داده می‌شود. 
همانطور که در ابتدای مطلب ذکر شد، بعد از این بازسازی کد می‌توان متغیرهای محلی موقتی را به صورت فیلدهای کلاس PriceCalculator تغییر داد و حتی متد را با سادگی بیشتری به چند متد تقسیم کرد. به طور مثال همچنین می‌توان روالهایی مانند محاسبه تخفیف و مالیات را نیز با استفاده از الگوی طراحی decorator در PriceCalculator پیاده سازی نمود.  

مراحل انجام این بازسازی کد  

  1. کلاس جدیدی بسازید و نام آن را بر اساس نام متد مورد نظر خود انتخاب نمایید. 
  2. به کلاس ساخته شده فیلدی برای نگه داری شیء کلاس مرجع (در مثال ما Order) متد اصلی ایجاد کنید.
  3. به کلاس جدید سازنده‌ای برای پاس دادن شیء کلاس مرجع (در مثال ما Order) ایجاد کنید. 
  4. در کلاس جدید متدی با نام مناسب (در مثال ما Compute) ایجاد کنید. 
  5. بدنه متد مورد نظر را به بدنه متد ایجاد شده در کلاس جدید کپی کنید و تمامی رفرنسها به اعضای کلاس مرجع را با رفرنس به شیء موجود آن تغییر دهید. 
  6. کد را کامپایل کنید. 
  7. فراخوانی متد اصلی را با فراخوانی متد شیء کلاس جدید ایجاد شده تغییر دهید.  
یکی از فواید مهم این بازسازی کد، جداسازی منطقی از کد که به صورت متد بوده و تبدیل آن به کلاس است. زمانیکه روالی به کلاس مستقلی تبدیل می‌شود، می‌توان از الگوهای طراحی دیگری برای بهبود بیشتر آن استفاده نمود. همچنین انتقال و استفاده دوباره آن نیز بسیار آسان‌تر خواهد شد. 
مطالب دوره‌ها
استفاده از StructureMap جهت تزریق وابستگی‌ها در برنامه‌های WPF و الگوی MVVM
در این قسمت قصد داریم همانند کنترلرها در ASP.NET MVC، کار تزریق وابستگی‌ها را در متدهای سازنده ViewModelهای WPF بدون استفاده از الگوی Service locator انجام دهیم؛ برای مثال:
    public class TestViewModel
    {
        private readonly ITestService _testService;
        public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس
        {
            _testService = testService;
        }
و همچنین کار اتصال یک ViewModel، به View متناظر آن‌را نیز خودکار کنیم. قراردادی را نیز در اینجا بکار خواهیم گرفت:
نام تمام Viewهای برنامه به View ختم می‌شوند و نام ViewModelها به ViewModel. برای مثال TestViewModel و TestView معرف یک ViewModel و View متناظر خواهند بود.


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

namespace DI07.Services
{
    public interface ITestService
    {
        string Test();
    }
}

namespace DI07.Services
{
    public class TestService: ITestService
    {
        public string Test()
        {
            return "برای آزمایش";
        }
    }
}
یک پروژه WPF را آغاز کرده و سپس یک پروژه Class library دیگر را به نام Services با دو کلاس و اینترفیس فوق، به آن اضافه کنید. هدف از این کلاس‌ها صرفا آشنایی با نحوه تزریق وابستگی‌ها در سازنده یک کلاس ViewModel در WPF است.


علامتگذاری ViewModelها

در ادامه یک اینترفیس خالی را به نام IViewModel مشاهده می‌کنید:
namespace DI07.Core
{
    public interface IViewModel // از این اینترفیس خالی برای یافتن و علامتگذاری ویوو مدل‌ها استفاده می‌کنیم
    {
    }
}
از این اینترفیس برای علامتگذاری ViewModelهای برنامه استفاده خواهد شد. این روش، یکی از انواع روش‌هایی است که در مباحث Reflection برای یافتن کلاس‌هایی از نوع مشخص استفاده می‌شود.
برای نمونه کلاس TestViewModel برنامه، با پیاده سازی IViewModel، به نوعی نشانه گذاری نیز شده است:
using DI07.Services;
using DI07.Core;

namespace DI07.ViewModels
{
    public class TestViewModel : IViewModel // علامتگذاری ویوو مدل
    {
        private readonly ITestService _testService;
        public TestViewModel(ITestService testService) //تزریق وابستگی در سازنده کلاس
        {
            _testService = testService;
        }

        public string Data
        {
            get { return _testService.Test(); }
        }
    }
}


تنظیمات آغازین IoC Container مورد استفاده

در کلاس استاندارد App برنامه WPF خود، کار تنظیمات اولیه StructureMap را انجام خواهیم داد:
using System.Windows;
using DI07.Core;
using DI07.Services;
using StructureMap;

namespace DI07
{
    public partial class App
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            ObjectFactory.Configure(cfg =>
            {
                cfg.For<ITestService>().Use<TestService>();

                cfg.Scan(scan =>
                {
                    scan.TheCallingAssembly();
                    // Add all types that implement IView into the container, 
                    // and name each specific type by the short type name.
                    scan.AddAllTypesOf<IViewModel>().NameBy(type => type.Name);
                    scan.WithDefaultConventions();
                });
            });
        }
    }
}
در اینجا عنوان شده است که اگر نیاز به نوع ITestService وجود داشت، کلاس TestService را وهله سازی کن.
همچنین در ادامه از قابلیت اسکن این IoC Container برای یافتن کلاس‌هایی که IViewModel را در اسمبلی جاری پیاده سازی کرده‌اند، استفاده شده است. متد NameBy، سبب می‌شود که بتوان به این نوع‌های یافت شده از طریق نام کلاس‌های متناظر دسترسی یافت.


اتصال خودکار ViewModelها به Viewهای برنامه

using System.Windows.Controls;
using StructureMap;

namespace DI07.Core
{
    /// <summary>
    /// Stitches together a view and its view-model
    /// </summary>
    public static class ViewModelFactory
    {
        public static void WireUp(this ContentControl control)
        {
            var viewName = control.GetType().Name;
            var viewModelName = string.Concat(viewName, "Model"); //قرار داد نامگذاری ما است
            control.Loaded += (s, e) =>
            {
                control.DataContext = ObjectFactory.GetNamedInstance<IViewModel>(viewModelName);
            };
        }
    }
}
اکنون که کار علامتگذاری ViewModelها انجام شده و همچنین IoC Container ما می‌داند که چگونه باید آن‌ها را در اسمبلی جاری جستجو کند، مرحله بعدی، ایجاد کلاسی است که از این تنظیمات استفاده می‌کند. در کلاس ViewModelFactory، متد WireUp، وهله‌ای از یک View را دریافت کرده، نام آن‌را استخراج می‌کند و سپس بر اساس قراردادی که در ابتدای بحث وضع کردیم، نام ViewModel متناظر را یافته و سپس زمانیکه این View بارگذاری می‌شود، به صورت خودکار DataContext آن‌را به کمک StructureMap وهله سازی می‌کند. این وهله سازی به همراه تزریق خودکار وابستگی‌ها در سازنده کلاس ViewModel نیز خواهد بود.


استفاده از کلاس ViewModelFactory

در ادامه کدهای TestView و پنجره اصلی برنامه را مشاهده می‌کنید:

<UserControl x:Class="DI07.Views.TestView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="{Binding Data}" />
    </Grid>
</UserControl>


<Window x:Class="DI07.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Views="clr-namespace:DI07.Views"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Views:TestView />
    </Grid>
</Window>
در فایل Code behind مرتبط با TestView تنها کافی است سطر فراخوانی this.WireUp اضافه شود تا کار تزریق وابستگی‌ها، وهله سازی ViewModel متناظر و همچنین مقدار دهی DataContext آن به صورت خودکار انجام شود:
using DI07.Core;

namespace DI07.Views
{
    public partial class TestView
    {
        public TestView()
        {
            InitializeComponent();
            this.WireUp(); //تزریق خودکار وابستگی‌ها و یافتن ویوو مدل متناظر
        }
    }
}

دریافت پروژه کامل این قسمت
  DI07.zip
مطالب
الگوی بازدیدکننده Visitor Pattern
این الگو یکی دیگر از الگوهای رفتاری است که به قاعده OCP یا Open Closed Principle کمک بسیاری می‌کند. این الگو برای زمانی مناسب است که ما سعی بر این داریم تا یک سری الگوریتم‌های متفاوت را بر روی یک سری از اشیاء پیاده سازی کنیم. به عنوان مثال تصور کنید که ما در یک سازمان افراد مختلفی را از مدیریت اصلی گرفته، تا ساده‌ترین کارمندان، داریم و برای محاسبه حقوق و مالیات و ... نیاز است تا برای هر کدام دستور العمل‌هایی را اجرا کنیم  و ممکن است در آینده تعداد این دستور العمل‌ها بالاتر هم برود.
در این مثال ما سه گروه Manager,Employee و Worker را داریم که می‌خواهیم با استفاده از این الگو برای هر کدام به طور جداگانه، حقوق و دستمزد و اضافه کاری را محاسبه کنیم. با توجه به اینکه فرمول هر یک جداست و این احتمال نیز وجود دارد که هر کدام خواص مخصوص به خود را داشته باشند که در دیگری وجود ندارد و در آینده این احتمال می‌رود که سمت جدید یا دستورالعمل‌های جدیدی اضافه شود، بهترین راه حل استفاده از الگوی Visitor است.

الگوی visitor دو بخش مهم دارد؛ یکی Element که قرار است کار روی آن انجام شود. مثل سمت‌های مختلف و دیگری Visitor هست که همان دستورالعمل‌هایی چون محاسبه حقوق و دستمزد و ... است که روی المان‌ها صورت می‌گیرد.
ابتدا برای هر کدام یک اینترفیس را با مشخصات زیر می‌سازیم:
 public interface IElement
    {
        void Accept(IElementVisitor visitor);
    }

    public interface IElementVisitor
    {
        void Visit(Manager manager);
        void Visit(Employee manager);
        void Visit(Worker manager);
    }
همانطور که می‌بینید در کلاس Visitor سه متد هستند که سه کلاس مدیر، کارمند و کارگر را که مشتق شده از اینترفیس Element هستند، به صورت آرگومان می‌پذیرند. توصیف هر کلاس المان به شرح زیر است:
 public class  Manager: IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

public class Employee: IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

public class Worker:IElement
    {
        public int WorkingHour = 8;
        public int Wife = 1;
        public int Children = 3;
        public int OffDays = 6;
        public int OverHours = 12;

        public void Accept(IElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }
ما اطلاعات هر کلاس را در این مثال، مشابه گذاشته‌ایم تا نتیجه فرمول را ببینیم. ولی هیچ الزامی به رعایت آن نیست.
حال وقت آن رسیده تا از روی کلاس Visitor، برای حقوق، دستمزد و اضافه کاری، کلاس‌های جدیدی را بسازیم:
 class SalaryCalculator:IElementVisitor
    {
        public void Visit(Manager manager)
        {
            var salary = manager.WorkingHour*10000;
            salary += manager.Wife*25000;
            salary += manager.Children*20000;
            salary -= manager.OffDays*5000;
            Console.WriteLine("Manager's Salary is " + salary);
        }

        public void Visit(Employee employee)
        {
            var salary = employee.WorkingHour * 7000;
            salary += employee.Wife * 15000;
            salary += employee.Children * 10000;
            salary -= employee.OffDays * 6000;
            Console.WriteLine("Employee's Salary is " + salary);
        }

        public void Visit(Worker worker)
        {
            var salary = worker.WorkingHour * 6000;
            salary += worker.Wife * 5000;
            salary += worker.Children * 2000;
            salary -= worker.OffDays * 7000;
            Console.WriteLine("Worker's Salary is " + salary);
        }
    }

    class WageCalculator:IElementVisitor
    {
        public void Visit(Manager manager)
        {
            var wage = manager.OverHours*30000;
            Console.WriteLine("Employee's wage is " + wage);
        }

        public void Visit(Employee employee)
        {
            var wage = employee.OverHours * 20000;
            Console.WriteLine("Employee's wage is " + wage);
        }

        public void Visit(Worker worker)
        {
            var wage = worker.OverHours * 15000;
            Console.WriteLine("Employee's wage is " + wage);
        }
    }
اکنون نیاز است تا ارتباط بین المان‌ها و بازدید کننده‌ها را طوری برقرار کنیم که برای تغییر آن‌ها در آینده، مشکلی نداشته باشیم. به همین جهت یک کلاس جدید به نام سیستم مالی ایجاد می‌کنیم:
class FinancialSystem
    {
        private readonly IList<IElement> _elements;

        public FinanceSystem()
        {
            _elements=new List<IElement>();
        }

        public void Attach(IElement element)
        {
            _elements.Add(element);
        }

        public void Detach(IElement element)
        {
            _elements.Remove(element);
        }

        public void Accept(IElementVisitor visitor)
        {
            foreach (var element in _elements)
            {
                element.Accept(visitor);
            }
        }
    }
در این روش تمام المان‌ها را داخل یک لیست قرار داده و سپس با استفاده از متد Accept، یکی از کلاس‌های مشتق شده از Visitor را به آن نسبت می‌دهیم که وظیفه آن صدا زدن متد Accept درون المان هاست. وقتی متد Accept المان‌ها صدا زده شد، شیء، المان را به متد Visit در Visitor داده و فرمول را روی آن اجرا می‌کند.
بدنه اصلی:
IElement manager=new Manager();
IElement employee=new Employee();
IElement worker=new Worker();

var fine=new FinancialSystem();
fine.Attach(manager);
fine.Attach(employee);
fine.Attach(worker);

fine.Accept(new SalaryCalculator());
fine.Accept(new WageCalculator());
نتیجه خروجی:
Manager's Salary is 135000
Employee's Salary is 65000
Worker's Salary is 17000
Manager's wage is 360000
Employee's wage is 240000
Worker's wage is 180000
مطالب
تست کردن Command/Command Handler ها در MediatR
شاید شما هم در پروژه خودتان نیاز داشته باشید تا اتصالات MediatR را بررسی یا به نوعی از صحت کدهایی که بر پایه MediatR زدید مطمئن شوید. در اینجا به بررسی نحوه Assert کردن اتصالات MediatR می‌پردازم. اول از همه باید به این فکر کرد که چه چیزی را می‌خواهیم تست کنیم؟ طبیعتا وقتی یک Command را ایجاد میکنیم، انتظار داریم که یک CommandHandler مختص به آن نیز ایجاد شده باشد. پس ما به بررسی ساختار کد می‌پردازیم.
برای شروع، یک Interface را با متد IsValid را ایجاد میکنیم. این Interface بعد‌ها توسط کلاس CommandValidator پیاده سازی می‌شود تا هر وظیفه، تشخیص درست بودن اتصالات command را چک کنند.
همانطور که مشخص است، متد IsValid دو پارامتر اسم ارسال کننده (Command,Query,Notification) و اسم Handler این ارسال کننده‌ها را دریافت میکند.
internal interface IValidation
    {
        bool IsValid(string sendNamesEndTo, string handlerNamesEndTo);
    }
کد زیر نحوه پیاده سازی IValidation را برای کلاس CommandValidator، نشان میدهد:
  public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();

            var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
                typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));

            var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
            if (!memberInfos.Any())
                throw new ArgumentException("Can not find any Command");

            var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));

            var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
            if (!typeInfos.Any())
                throw new ArgumentException("Can not find any CommandHandler");

            if (typeInfos.Count() != memberInfos.Count())
                return false;

            return !(from typeInfo in memberInfos
                let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
                where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
                select typeInfo).Any();
        }
همانطور که مشخص است، برای دو پارامتر ورودی، مقادیر پیش فرض Command و CommandHandler در نظر گرفته شده‌اند. توجه داشته باشید این اسم کامل نیست و تنها نام انتهای کلاس است: برای مثال OrderCommand/OrderCommandHandler:
public bool IsValid(string commandNamesEndTo = "Command", string commandHandlersEndTo = "CommandHandler")
داخل بدنه متد، ابتدا تمام Assembly‌های موجود در App را لیست میکنیم :
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
سپس تمام کلاس‌های تعریف شده‌ای را که از IBaseRequest ارث بری کرده و انتهای نام آن‌ها شامل commandNamesEndTo باشد، لیست می‌کند. می‌توان گفت تا اینجا تمام Command‌ها را پیدا کرده‌ایم. در صورتیکه لیست موجود خالی باشد، یعنی یک‌جای کار مشکل دارد؛ شاید کلا Command ای تعریف نشده یا ... پس یک ArgumentException را throw میکنیم.
            var commandTypeInfos = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandNamesEndTo.ToLower()) &&
                typeInfo.ImplementedInterfaces.Any(type => type == typeof(IBaseRequest))));

            var memberInfos = commandTypeInfos as TypeInfo[] ?? commandTypeInfos.ToArray();
            if (!memberInfos.Any())
                throw new ArgumentException("Can not find any Command");
در مرحله‌ی بعدی، تمام Handler‌ها را پیدا و لیست میکنیم. در اینجا نیز مانند کد بالا، اگر لیست خالی باشد، به این معناست که هیچ handler ای تعریف نشده‌است.
  var handlerTypeInfo = assemblies.SelectMany(x => x.DefinedTypes.Where(typeInfo =>
                typeInfo.Name.ToLower().EndsWith(commandHandlersEndTo.ToLower())));

            var typeInfos = handlerTypeInfo as TypeInfo[] ?? handlerTypeInfo.ToArray();
            if (!typeInfos.Any())
                throw new ArgumentException("Can not find any CommandHandler");
مرحله بعدی، بررسی کردن تعداد Command‌ها و CommandHandler‌ها می‌باشد. طبیعتا باید مقدار این دو برابر باشند؛ چرا که هر Command، نیاز به یک  CommandHandler نیز دارد. بنابراین اگر تعداد این دو یکسان نبود، ساختار ما درست نیست؛ پس مقدار false برگشت داده می‌شود : 
    if (typeInfos.Count() != memberInfos.Count())
                return false;
بعد از تمام این ماجرا‌ها، به مرحله آخر میرسیم؛ آن هم چک کردن نظیر به نظیر Command‌ها با CommandHandler‌ها می‌باشد. به این صورت که اگر به ازای هر Command تعریف شده یک CommandHandler با GenericTypeArguments از نوع command وجود داشته باشد، می‌توان گفت که ساختار ما درست تعریف شده‌است.
return !(from typeInfo in memberInfos
                let interfaces = typeInfos.SelectMany(x => x.ImplementedInterfaces)
                where interfaces.Any(x => x.GenericTypeArguments.All(type => type != typeInfo))
                select typeInfo).Any();
توجه داشته باشید برای تست کردن قسمت‌هایی مثل Query و Notification نیز می‌توان از همین فرآیند بهره برد؛ البته با کمی تغییر در پیاده سازی متد IsValid.
در نهایت برای استفاده می‌توان به شکل زیر کد‌ها را استفاده کرد : 
var validCommandConfiguration = new CommandValidator().IsValid();

برای دیدن کد‌های کامل پیاده سازی تست Command/Query/Notification، می‌توانید از لینک گیت هاب زیر استفاده کنید.
مطالب
ایجاد یک Abstract Factory با استفاده از جنریک‌ها
همان طور که میدانید از الگوی Factory به عنوان روشی برای کاهش وابستگی اجزای یک سیستم استفاده میشود. در این مقاله میخواهیم با استفاده از جنریک‌ها، الگوی Abstract Factory را پیاده سازی کنیم.
1) ایجاد یک کلاس به نام AbstractFactory و یک متد جنریک به نام CreateObject
public class AbstractFactory
    {
        public static T CreateObject<T>() where T : class , new()
        {
            return new T();
        }
    }
2) ساخت کلاسهای مورد نظر
public class Product
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Product Class Craeted. ");
        }
    }

public class Category
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Category Class Created.");
        }
    }

3) حال در یک برنامه‌ی کنسول ویندوز، از کلاس AbstractFactory به شکل زیر استفاده میکنیم
static void Main(string[] args)
        {
            var p = AbstractFactory.CreateObject< Product>();
            p.DisplayInfo();
            Console.WriteLine("======");

            var c = AbstractFactory.CreateObject<Category>();
            c.DisplayInfo();
            Console.WriteLine("======");
            
            Console.ReadKey();
        }
خروجی کد بالا

مطالب
Test Driven Development #3
در پست قبلی با  نوشتن یک تست ساده، با مفهوم TDD بیشتر آشنا شدیم .در این پست قصد بر این  است که به وسیله Mvc.Net شروع به نوشتن تست‌های جدی‌تر کرده و از مزایای آن بهره ببریم .
برای  شروع یک پروژه Mvc.Net ساخته و Nunit را در آن نصب می‌کنیم.
مدل زیر را در پوشه مدل‌ها می‌سازیم:
    public class Idea
    {
        public static List<Idea> Ideas = new List<Idea>
            {
                new Idea{Content="سایتی که در ایده به اشتراک گذاشته شود",Title = "سایت ایده ها"},
                new Idea{Content="عینک گوگل را فارسی کنم",Title = "عینک گوگل"},
            };
        public string Content { get;  set;}
        public string Title { get; set; }
    }
  [TestFixture]
    public class IdeaTest
    {
        [Test]
        public void ShouldDisplayListOfIdea()
        {
            var viewResult = new IdeaController().Index() as ViewResult;
            Assert.AreEqual(Idea.Ideas, viewResult.Model)
             Assert.IsNotNull(viewResult.Model);


        }    
 }

کد بالا شامل مقایسه مقدار خروجی Action با لیستی از مدل Idea و همچنین اطمینان از خالی نبودن مدل ارسالی به view می‌باشد.در خط اول یک وهله از Controller می سازیم و Action مورد نظر را به شی از جنس ViewResult  تبدیل(Cast) می‌کنیم پس از آن به وسیله viewResult.Model به مدلی که به سمت view پاس داده می‌شود دسترسی خواهیم داشت.اکنون اگر تست را اجرا  کنیم با خطای کامپایل مواجه می‌شویم.حال Controller و Action مورد نظر را به صورتی  که تست ما پاس شود پیاده سازی می‌کنیم.
    public class IdeaController : Controller
    {

        public ActionResult Index()
        {
            return View(Idea.Ideas);
        }
      }
کد بالا مقدار Ideas را به view برمیگرداند.
در این دروره ما به تست کردن ویو‌ها نخواهیم پرداخت.
تست بعدی تست ساده ای است که فقط میخواهیم از از وجود داشتن یک Action و نام view بازگشتی اطمینان حاصل کنیم.
   [Test]
        public void ShouldLoadCreateIdeaView()
        {
            var viewResult = new IdeaController().Create() as ViewResult;
            Assert.AreEqual(string.Empty, viewResult.ViewName);
        }
در کد بالا مثل تست قبل، یک وهله از Controller می سازیم و سپس نام view بازگشتی را با string.Empty مقایسه میکنیم به این معنی که view خروجی Action ما نباید نامی داشته باشد و براساس قرار دادها باید هم نام اکشن باشد.
حال نوبت به پیاده سازی اکشن رسید.:
        public ActionResult Create()
        {
            return View();
        }
در تست بعدی میخواهیم عملیات اضافه شدن یک Idea را به لیست بررسی کنیم:
    [Test]
        public void ShouldAddIdeaItem()
        {
            var idea = new Idea { Title = "شبکه اجتماعی ", Content = " شبکه اجتماعی سینمایی" };
            var redirectToRouteResult = new IdeaController().Create(idea) as RedirectToRouteResult;
            Assert.Contains(idea, Idea.Ideas);
            Assert.AreEqual("Index",redirectToRouteResult.RouteValues["action"]);
        }
تست بالا نیز مانند دو تست قبل است با این تفاوت که مخواهیم ریدارکت شدن به یک Action  خاص را نیز تست کنیم.برای همین مقدار خروجی را به RedirectToRouteResult تبدیل می‌کنیم.در ادامه یک Idea جدید ساخته و به لست اضافه میکنیم سپس از وجود داشتن آن در لیست Ideas اطمینان حاصل می‌کنیم.در خط آخر نیز نام Action که انتظار داریم بعد از اضافه شدن یک Idea ,کاربر به آن هدایت شود را ست می‌کنیم.
پیاده سازی Action  به شکل زیر است:
        public ActionResult Create(Idea idea)
        {
            Idea.Ideas.Add(idea);
            return RedirectToAction("Index");
        }

در این پست شما با مدل تست نویسی برایMvc.Net آشنا شدید.در مطلب بعدی شما با تست حذف و اصلاح Ideas آشنا خواهید شد.