مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت چهارم

همانطور که قول داده بودم، به اصول GRASP می‌پردازیم.

اصول GRASP-General Responsibility Assignment Software Principles

این اصول به بررسی نحوه تقسیم وظایف بین کلاس‌ها و مشارکت اشیاء برای به انجام رساندن یک مسئولیت می‌پردازند. اینکه هر کلاس در ساختار نرم افزار چه وظیفه‌ای دارد و چگونه با کلاس‌های دیگر مشارکت میکند تا یک عملکرد به سیستم اضافه گردد. این اصول به چند بخش تقسیم می­شوند:

  • کنترلر ( Controller )
  • ایجاد کننده ( Creator )
  • انسجام قوی ( High Cohesion )
  • واسطه گری ( Indirection )
  • دانای اطلاعات ( Information Expert )
  • اتصال ضعیف ( Low Coupling )
  • چند ریختی ( Polymorphism )
  • حفاظت از تاثیر تغییرات ( Protected Variations )
  • مصنوع خالص ( Pure Fabrication )

 

Controller

این الگو بیان می‌کند که مسئولیت پاسخ به رویداد‌های (Events ) یک سناریوی محدود مانند یک مورد کاربردی ( Use Case ) باید به عهده یک کلاس غیر UI باشد. کنترلر باید کارهایی را که نیاز است در پاسخ رویداد انجام شود، به دیگران بسپرد و نتایج را طبق درخواست رویداد بازگرداند. در اصل، کنترلر دریافت کننده رویداد، راهنمای مسیر پردازش برای پاسخ به رویداد و در نهایت برگرداننده پاسخ به سمت مبداء رویداد است. در زیر مثالی را می‌بینیم که رویداد اتفاق افتاده توسط واسط گرافیکی به سمت یک handler (که متدی است با ورودیِ فرستنده و آرگمانهای مورد نیاز) در کنترلر فرستاده میشود. این روش event handling، در نمونه‌های وب فرم و ویندوز فرم دیده میشود. به صورتی خود کلاس‌های .Net وظیفه Event Raising از سمت UI با کلیک روی دکمه را انجام میدهد: 

 public class UserController
 {        
        protected void OnClickCreate(object sender, EventArgs e)
        {
           // call validation services
           // call create user services
        }
 }


در مثال بعد عملیات مربوط به User در یک WebApiController پاسخ داده میشود. در اینجا به جای استفاده از Event Raising برای کنترل کردن رویداد، از فراخوانی یک متد در کنترلر توسط درخواست HttpPost انجام میگیرد. در اینجا نیاز است که در سمت کلاینت درخواستی را ارسال کنیم:

    public class UserWebApiController
    {
        [HttpPost]
        public HttpResponseMessage Create(UserViewModel user)
        {
            // call validation services
            // call create user services
        }
    }



Creator :

  این اصل میگوید شیء ای میتواند یک شیء دیگر را بسازد ( instantiate ) که: (اگر کلاس B بخواهد کلاس A را instantiate کند)

  • کلاس B شیء از کلاس A را در خود داشته باشد؛
  • یا اطلاعات کافی برای instantiate کردن از A را داشته باشد؛
  • یا به صورت نزدیک با A در ارتباط باشد؛
  • یا بخواهد شیء A را ذخیره کند.

از آنجایی که این اصل بدیهی به نظر میرسد، با مثال نقض، درک بهتری را نسبت به آن میتوان پیدا کرد:

    // سازنده
    public class B
    {
        public static A CreateA(string name, string lastName, string job)
        {
            return new A() {
                Name =name,
                LastName = lastName,
                Job = job
            };
        }
    }
    // ایجاد شونده
    public class A
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public string Job { get; set; }
    }

    public class Context
    {
        public void Main()
        {
            var name = "Rasoul";
            var lastName = "Abbasi";
            var job = "Developer";            
            var obj = B.CreateA(name, lastName, job);
        }
    }


و اما چرا این مثال، اصل Creator را نقض میکند. در مثال میبینید که کلاس B، یک شیء از نوع A را در متد Main کلاس Context ایجاد میکند. کلاس B فقط یک متد برای تولید A دارد و در عملیات تولید A هیچ منطق خاصی را پیاده سازی نمیکند.کلاس B شیء ای را از کلاس A ، در خود ندارد، با آن ارتباط نزدیک ندارد و آنرا ذخیره نمیکند. با اینکه کلاس B اطلاعات کافی را برای تولید A از ورودی میگیرد، ولی این کلاس Context است که اطلاعات کافی را ارسال مینماید. اگر در کلاس B منطقی اضافه بر instance گیریِ ساده وجود داشت (مانند بررسی صحت و اعتبار سنجی)، میتوانستیم بگوییم کلاس B از یک مجموعه عملیات instance گیری با خبر است که کلاس Context  نباید از آن خبر داشته باشد. لذا اکنون هیچ دلیلی وجود ندارد که وظیفه تولید A را در Context انجام ندهیم و این مسئولیت را به کلاس B منتقل کنیم. این مورد ممکن است در ذهن شما با الگوی Factory تناقض داشته باشد. ولی نکته اصلی در الگو Factory انجام عملیات instance گیری با توجه به منطق برنامه است؛ یعنی وظیفه‌ای که کلاس Context نباید از آن خبر داشته باشد را به کلاس Factory منتقل میکنیم. در غیر اینصورت ایجاد کلاس Factory بی معنا خواهد بود (مگر به عنوان افزایش انعطاف پذیری معماری که بتوان به راحتی نوع پیاده سازی یک واسط را تغییر داد).


High Cohesion :

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

در مثال زیر نقض این اصل را مشاهده میکنیم:

    class Controller
    {
        public void CreateProduct(string name, int categoryId) { }
        public void EditProduct(int id, string name) { }
        public void DeleteProduct(int id) { }
        public void CreateCategory(string name) { }
        public void EditCategory(int id, string name) { }
        public void DeleteCategory(int id) { }
    }  

همانطور که میبینید، کلاس کنترلر ما، مسئولیت مدیریت Product و Category را بر عهده دارد. بزرگ شدن این کلاس، باعث سخت‌تر شدن خواندن کد و رفع اشکال میگردد. با جداسازی کنترلر مربوط به Product از Category میتوان انسجام را بالا برد.


Indirection :

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

الگوهای Adapter و Delegate و همچنین نقش کنترلر در الگوی معماری MVC از این اصل پیروی میکنند. 

    class SenderA
    {
        public Mediator mediator { get; }
        public SenderA() { mediator = new Mediator(); }
        public void Send(string message, string reciever) { mediator.Send(message, reciever); }
    }
    class SenderB
    {
        public Mediator mediator { get; }
        public SenderB() { mediator = new Mediator(); }
        public void Send(string message) { }
    }

    public class RecieverA
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "create":
                    break;
                case "delete":
                    break;
                default:
                    break;
            }
        }
    }
    public class RecieverB
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "edit":
                    break;
                case "rollback":
                    break;
                default:
                    break;
            }
        }
    }
    class Mediator
    {
        internal void Send(string message, string reciever)
        {
            switch (reciever)
            {
                case "A":
                    var recieverObjA = new RecieverA();
                    recieverObjA.DoAction(message);
                    break;
                case "B":
                    var recieverObjB = new RecieverB();
                    recieverObjB.DoAction(message);
                    break;

                default:
                    break;
            }
        }
    }
    class IndirectionContext
    {
        public void Main()
        {
            var senderA = new SenderA();
            senderA.Send("rollback", "B");
            var senderB = new SenderA();
            senderB.Send("create", "A");

        }
    }

در این مثال کلاس Mediator به عنوان واسط ارتباطی بین کلاس‌های Sender و Receiver قرار گرفته و نقش تحویل پیغام را دارد.

در مقاله بعدی، به بررسی سایر اصول GRASP خواهم پرداخت.

نظرات مطالب
C# 7 - Tuple return types and deconstruction
برای بسط دادن یک چندتایی به آرگومان‌های ورودی یک متد، آیا راهکاری در نظر گرفته شده است؟
مثلا چیزی شبیه spread در ES6
public void showMsg(int age, string name){/*...*/}

 (int age, string name) value =(20, "Jessy"); showMsg(...value); //? or something else
مطالب
WebStorage: قسمت دوم
در این مقاله قصد داریم نحوه‌ی کدنویسی webstorage را با کتابخانه‌هایی که در مقاله قبل معرفی کردیم بررسی کنیم.
ابتدا روش ذخیره سازی و بازیابی متداول آن را بررسی میکنیم که تنها توسط دو تابع صورت می‌گیرد. مطلب زیر برگرفته از w3Schools است:
دسترسی به شیء webstorage به صورت زیر امکان پذیر است:
window.localStorage
window.sessionStorage
ولی بهتر است قبل از ذخیره و بازیابی، از پشتیبانی مرورگر از webstorage اطمینان حاصل نمایید:
if(typeof(Storage) !== "undefined") {
    // Code for localStorage/sessionStorage.
} else {
    // Sorry! No Web Storage support..
}
برای ذخیره سازی و سپس خواندن به شکل زیر عمل می‌کنیم:
localStorage.setItem("lastname", "Smith");

//========================
localStorage.getItem("lastname");
خواندن می‌تواند حتی به شکل زیر هم صورت بگیرد:
var a=localStorage.lastname;
استفاده از store.js برای مرورگرهایی که از webstorage پشتیبانی نمی‌کنند به شکل زیر است:
//ذخیره مقدار
store.set('username', 'marcus')

//بازیابی مقدار
store.get('username')

//حذف مقدار
store.remove('username')

//حذف تمامی مقادیر ذخیره شده
store.clear()

//ذخیره ساختار
store.set('user', { name: 'marcus', likes: 'javascript' })

//بازیابی ساختار به شکل قبلی
var user = store.get('user')
alert(user.name + ' likes ' + user.likes)

//تغییر مستقیم مقدار قبلی
store.getAll().user.name == 'marcus'

//بازخوانی تمام مقادیر ذخیر شده توسط یک حلقه
store.forEach(function(key, val) {
    console.log(key, '==', val)
})
همچنین بهتر هست از یک فلگ برای بررسی فعال بودن storage استفاده نمایید. به این دلیل که گاهی کاربرها از پنجره‌های private استفاده می‌کنند که ردگیری اطلاعات آن ممکن نیست و موجب خطا می‌شود.
<script src="store.min.js"></script>
<script>
    init()
    function init() {
        if (!store.enabled) {
            alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.')
            return
        }
        var user = store.get('user')
        // ... and so on ...
    }
</script>
در صورتیکه بخشی از داده‌ها را توسط localstorage ذخیره نمایید و بخواهید از طریق storage به آن دسترسی داشته باشید، خروجی string خواهد بود؛ صرف نظر از اینکه شما عدد، شیء یا آرایه‌ای را ذخیره کرده‌اید.
در صورتیکه ساختار JSON را ذخیره کرده باشید، می‌توانید رشته برگردانده شده را با json.stringify و json.parse بازیابی و به روز رسانی کنید.
در حالت cross browser تهیه‌ی یک sessionStorage امکان پذیر نیست. ولی می‌توان به روش ذیل و تعیین یک زمان انقضاء آن را محدود کرد:
var storeWithExpiration = {
// دریافت کلید و مقدار و زمان انقضا به میلی ثانیه
    set: function(key, val, exp) {
//ایجاد زمان فعلی جهت ثبت تاریخ ایجاد
        store.set(key, { val:val, exp:exp, time:new Date().getTime() })
    },
    get: function(key) {
        var info = store.get(key)
//در صورتی که کلید داده شده مقداری نداشته باشد نال را بر میگردانیم
        if (!info) { return null }
//تاریخ فعلی را منهای تاریخ ثبت شده کرده و در صورتی که
//از مقدار میلی ثاینه بیشتر باشد یعنی منقضی شده و نال بر میگرداند
        if (new Date().getTime() - info.time > info.exp) { return null }
        return info.val
    }
}

// استفاده عملی از کد بالا
// استفاده از تایمر جهت نمایش واکشی داده‌ها قبل از نقضا و بعد از انقضا
storeWithExpiration.set('foo', 'bar', 1000)
setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 500) // -> "bar"
setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 1500) // -> null

مورد بعدی استفاده از سورس  cross-storage  است. اگر به یاد داشته باشید گفتیم یکی از احتمالاتی که برای ما ایجاد مشکل می‌کند، ساب دومین هاست که ممکن است دسترسی ما به یک webstorage را از ساب دومین دیگر از ما بگیرد.
این کتابخانه به دو جز تقسیم شده است یکی هاب Hub و دیگری Client .
ابتدا نیاز است که هاب را آماده سازی و با ارائه یک الگو از آدرس وب، مجوز عملیات را دریافت کنیم. در صورتیکه این مرحله به فراموشی سپرده شود، انجام هر نوع عمل روی دیتاها در نظر گرفته نخواهد شد.
CrossStorageHub.init([
  {origin: /\.example.com$/,            allow: ['get']},
  {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);
حرف $ در انتهای عبارت باعث مشود که دامنه‌ها با دقت بیشتری در Regex بررسی شوند و دامنه زیر را معتبر اعلام کند:
valid.example.com
ولی دامنه زیر را نامعتبر اعلام می‌کند:
invalid.example.com.malicious.com
همچنین می‌توانید تنظیماتی را جهت هدر‌های CSP و CORS، نیز اعمال نمایید:
{
  'Access-Control-Allow-Origin':  '*',
  'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE',
  'Access-Control-Allow-Headers': 'X-Requested-With',
  'Content-Security-Policy':      "default-src 'unsafe-inline' *",
  'X-Content-Security-Policy':    "default-src 'unsafe-inline' *",
  'X-WebKit-CSP':                 "default-src 'unsafe-inline' *",
}
پس کار را بدین صورت آغاز می‌کنیم، یک فایل به نام hub.htm درست کنید و هاب را آماده سازید:
hub.htm
<script type="text/javascript" src="~/Scripts/cross-storage/hub.js"></script>
<script>
 CrossStorageHub.init([
      {origin: /.*localhost:300\d$/, allow: ['get', 'set', 'del']}
    ]);
  </script>
کد بالا فقط درخواست‌های هاست لوکال را از پورتی که ابتدای آن با 300 آغاز می‌شود، پاسخ می‌دهد و مابقی درخواست‌ها را رد می‌کند. متدهای ایجاد، ویرایش و حذف برای این آدرس معتبر اعلام شده است. 
در فایل دیگر که کلاینت شناخته می‌شود باید فایل hub معرفی شود تا تنظیمات هاب خوانده شود:
var storage = new CrossStorageClient('http://localhost:3000/example/hub.html');

var setKeys = function () {
      
return storage.set('key1', 'foo').then(function() {
return storage.set('key2', 'bar');
      });
};
در خط اول، فایل هاب معرفی شده و تنظیمات روی این صفحه اعمال می‌شود. سپس در خطوط بعدی داده‌ها ذخیره می‌شوند. از آنجا که با هر یکبار ذخیره، return صورت می‌گیرد و تنها اجازه‌ی ورود یک داده را داریم، برای حل این مشکل متد then پیاده سازی شده است. متغیر setKeys شامل یک آرایه از کلیدها خواهد بود.
نحوه‌ی ذخیره سازی بدین شکل هم طبق مستندات صحیح است:
storage.onConnect().then(function() {

  return storage.set('key', {foo: 'bar'});

}).then(function() {

  return storage.set('expiringKey', 'foobar', 10000);

});
در کد بالا ابتدا یک داده دائم ذخیره شده است و در کد بعد یک داده موقت که بعد از 10 ثانیه اعتبار خود را از دست می‌دهد.
برای خواندن داده‌های ذخیره شده به نحوه زیر عمل می‌کنیم:
storage.onConnect().then(function() {
  return storage.get('key1');
}).then(function(res) {
  return storage.get('key1', 'key2', 'key3');
}).then(function(res) {
  // ...
});
کد بالا نحوه‌ی خواندن مقادیر را به شکل‌های مختلفی نشان میدهد و مقدار بازگشتی آن‌ها یک آرایه از مقادیر است؛ مگر اینکه تنها یک مقدار برگشت داده شود. مقدار بازگشتی در تابع بعدی به عنوان یک آرگومان در دسترس است. در صورتی که خطایی رخ دهد، قابلیت هندل آن نیز وجود دارد:
storage.onConnect()
    .then(function() {
      return storage.get('key1', 'key2');
    })

.then(function(res) {
      console.log(res); // ['foo', 'bar']
    })['catch'](function(err) {
      console.log(err);
    });
برای باقی مسائل چون به دست آوردن لیست کلیدهای ذخیره شده، حذف کلید‌های مشخص شده، پاکسازی کامل داده‌ها و ... به مستندات رجوع کنید.
در اینجا جهت سازگاری با مرورگرهای قدیمی خط زیر را به صفحه اضافه کنید:
<script src="https://s3.amazonaws.com/es6-promises/promise-1.0.0.min.js"></script>
ذخیره‌ی اطلاعات به شکل یونیکد، فضایی دو برابر کدهای اسکی میبرد و با توجه به محدود بودن حجم webstorage به 5 مگابایت ممکن است با کمبود فضا مواجه شوید. در صورتیکه قصد فشرده سازی اطلاعات را دارید می‌توانید از کتابخانه lz-string استفاده کنید. ولی توجه به این نکته ضروری است که در صورت نیاز، عمل فشرده سازی را انجام دهید و همینطوری از آن استفاده نکنید.

IndexedDB API
آخرین موردی که بررسی می‌شود استفاده از IndexedDB API است که با استفاده از آن میتوان با webstorage همانند یک دیتابیس رفتار کرد و به سمت آن کوئری ارسال کرد. 
var request = indexedDB.open("library");

request.onupgradeneeded = function() {
  // The database did not previously exist, so create object stores and indexes.
  var db = request.result;
  var store = db.createObjectStore("books", {keyPath: "isbn"});
  var titleIndex = store.createIndex("by_title", "title", {unique: true});
  var authorIndex = store.createIndex("by_author", "author");

  // Populate with initial data.
  store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
  store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
  store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
};

request.onsuccess = function() {
  db = request.result;
};
کد بالا ابتدا به دیتابیس library متصل می‌شود و اگر وجود نداشته باشد، آن را می‌سازد. رویداد onupgradeneeded برای اولین بار اجرا شده و در آن می‌توانید به ایجاد جداول و اضافه کردن داده‌های اولیه بپردازید؛ یا اینکه از آن جهت به ارتقاء ورژن دیتابیس استفاده کنید. خصوصیت result، دیتابیس باز شده یا ایجاد شده را باز می‌گرداند. در خط بعدی جدولی با کلید کد ISBN کتاب تعریف شده است. در ادامه هم دو ستون اندیس شده برای عنوان کتاب و نویسنده معرفی شده است که عنوان کتاب را یکتا و بدون تکرار در نظر گرفته است. سپس در جدولی که متغیر store به آن اشاره می‌کند، با استفاده از متد put، رکوردها داخل آن درج می‌شوند. در صورتیکه کار با موفقیت انجام شود رویداد onSuccess فراخوانی می‌گردد.
برای انجام عملیات خواندن و نوشتن باید از تراکنش‌ها استفاده کرد:
var tx = db.transaction("books", "readwrite");
var store = tx.objectStore("books");

store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});

tx.oncomplete = function() {
  // All requests have succeeded and the transaction has committed.
};
در خط  اول ابتدا یک خط تراکنشی بین ما و جدول books با مجوز خواندن و نوشتن باز می‌شود و در خط بعدی جدول books را در اختیار می‌گیریم و همانند کد قبلی به درج داده‌ها می‌پردازیم. در صورتیکه عملیات با موفقیت به اتمام برسد، متغیر تراکنش رویدادی به نام oncomplete فراخوانی می‌گردد. در صورتیکه قصد دارید تنها مجوز خواندن داشته باشید، عبارت readonly را به کار ببرید.
var tx = db.transaction("books", "readonly");
var store = tx.objectStore("books");
var index = store.index("by_author");

var request = index.openCursor(IDBKeyRange.only("Fred"));
request.onsuccess = function() {
  var cursor = request.result;
  if (cursor) {
    // Called for each matching record.
    report(cursor.value.isbn, cursor.value.title, cursor.value.author);
    cursor.continue();
  } else {
    // No more matching records.
    report(null);
  }
};
در دو خط اول مثل قبل، تراکنش را دریافت می‌کنیم و از آنجا که می‌خواهیم داده را بر اساس نام نویسنده واکشی کنیم، ستون اندیس شده نام نویسنده را دریافت کرده و با استفاده از متد opencursor درخواست خود را مبنی بر واکشی رکوردهایی که نام نویسنده fred است، ارسال میداریم. در صورتیکه عملیات با موفقیت انجام گردد و خطایی دریافت نکنیم رویداد onsuccess فراخوانی می‌گردد. در این رویداد با دو حالت برخورد خواهیم داشت؛ یا داده‌ها یافت می‌شوند و رکوردها برگشت داده می‌شوند یا هیچ رکوردی یافت نشده و مقدار نال برگشت خواهد خورد. با استفاده از cursor.continue می‌توان داده‌ها را به ترتیب واکشی کرده و مقادیر رکورد را با استفاده خصوصیت value  به سمت تابع report ارسال کرد.
کدهای بالا همه در مستندات معرفی شده وجود دارند و ما پیشتر توضیح ابتدایی در مورد آن دادیم و برای کسب اطلاعات بیشتر می‌توانید به همان مستندات معرفی شده رجوع کنید. برای idexedDB هم می‌توانید از این منابع +  +  +  استفاده کنید که خود w3c منبع فوق العاده‌تری است.

مطالب
نمایش تاریخ بر حسب تعداد روزهای گذشته
در بیشتر وب سایت‌های شاهد نمایش تاریخ بر حسب تعداد روز/ ماه و یا سال گذشته شده از آن تاریخ هستیم. برای نمونه در سایت جاری تاریخ را بر همین اساس نمایش می‌دهند. نمونه‌ای از آن مانند «در ‫۲ سال قبل، چهار شنبه ۲۷ دی ۱۳۹۱، ساعت ۰۳:۳۵» می‌باشد. در این مقاله قصد دارم کدهایی را جهت انجام این کار ارائه کنم. در این مثال که در ادامه شاهد آن خواهیم بود، از یک پروژه‌ی Win form ساده، جهت نمایش بهتر استفاده کرده‌ام.
جهت اینکه درک کد و یا توضیح آن نیز ساده‌تر صورت بگیرد، به نظرم ابتدا متد‌های مورد استفاده و کلاس‌هایی را که از آنها استفاده کرده‌ام، معرفی کنم بهتر باشد:
از کلاس Persiancalender جهت گرفتن روز/ماه و سال استفاده شده و سه متد به شرح زیر دارد:
GetHour : از این متد برای گرفتن ساعت تاریخ مورد نظر استفاده می‌شود. کد این متد به صورت زیر است:
 public string GetHour(DateTime lastdate)
        {
            PersianCalendar pc = new PersianCalendar();
            string result = " ساعت " + (((pc.GetHour(lastdate)) < 10) ? ("0" + pc.GetHour(lastdate).ToString()) : (pc.GetHour(lastdate)).ToString()) + ":" + (((pc.GetMinute(lastdate)) < 10) ? ("0" + pc.GetMinute(lastdate).ToString()) : (pc.GetMinute(lastdate)).ToString());
            return result;
        }
توضیح: اگر ساعت یا دقیقه تک رقمی باشد، یعنی کمتر از 10، برای نمایش بهتر آن یک صفر را به ابتدای آن اضافه می‌کنیم. یعنی ساعت 1:5 تبدیل می‌شود به 01:05
متد getDay : از این متد برای گرفتن نام روز مورد نظر استفاده می‌شود. ورودی این متد یک enum  از نوع DayOfWeek است:
public string getDay(DayOfWeek day)
        {
            string Result = "";
            switch (day)
            {
                case DayOfWeek.Friday:
                    Result = "جمعه";
                    break;
                case DayOfWeek.Monday:
                    Result = "دوشنبه";
                    break;
                case DayOfWeek.Saturday:
                    Result = "شنبه";
                    break;
                case DayOfWeek.Sunday:
                    Result = "یکشنبه";
                    break;
                case DayOfWeek.Thursday:
                    Result = "پنج شنبه";
                    break;
                case DayOfWeek.Tuesday:
                    Result = "سه شنبه";

                    break;
                case DayOfWeek.Wednesday:
                    Result = "چهارشنبه";
                    break;
                default:
                    break;
            }
            return Result;
        }
و در هر جایی نیاز به گرفتن تاریخ باشد، به صورت زیر عمل خواهیم کرد:
getDay(pc.GetDayOfWeek(LastDate))
pc یک متغییر از نوع persianclander می‌باشد.
متد GetMounth : همانطور که از نام این متد معلوم است، کار آن بازگشت نام ماه مورد استفاد است. کد آن نیز به صورت زیر می‌باشد:
public string GetMounth(int month)
        {
            string[] monthInYear = {"فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور","مهر","آبان","آذر","دی","بهمن","اسفند" };
            return monthInYear[month-1];
        }
وجود -1 در هنگام return به این دلیل است که زمانیکه قصد دریافت شماره ماه را از شیء PersianClander داشته باشیم، از یک شروع می‌شود. یعنی برای ماه اسفند مقدار 12 و برای ماه فروردین مقدار 1 و در یک آرایه، ایندکس‌ها از صفر شروع می‌شوند.
و اما کد کامل آن برای تبدیل تاریخ، به صورت رشته مورد نظر، به صورت زیر است:
private void btnGetDate_Click(object sender, EventArgs e)
        {
            DateTime LastDate = DateTime.Parse(txtLastDate.Text);
            TimeSpan ts = DateTime.Now - LastDate;
            PersianCalendar pc = new PersianCalendar();
            int DifferenceYear = DateTime.Now.Year - LastDate.Year;
            int DiffernceMounth = DateTime.Now.Month - LastDate.Month;
            if(DateTime.Now.Month>LastDate.Month)
                DiffernceMounth = DateTime.Now.Month - LastDate.Month; 
            else
            DiffernceMounth=LastDate.Month-DateTime.Now.Month;
            int DifferenceDays = ts.Days;

            StringBuilder Result = new System.Text.StringBuilder("");
           
            if(DifferenceYear>0)
            {
                Result.Append(DifferenceYear.ToString() + " سال پیش"+" ، "+getDay(pc.GetDayOfWeek(LastDate))+" "+pc.GetDayOfMonth(LastDate).ToString()+" " + GetMounth(pc.GetMonth(LastDate))+" " +pc.GetYear(LastDate)+GetHour(LastDate));
            }
            else if(DiffernceMounth>0)
            {
                Result.Append(DiffernceMounth.ToString() + " ماه پیش" + " ، " + getDay(pc.GetDayOfWeek(LastDate)) + " " + pc.GetDayOfMonth(LastDate).ToString() + " " + GetMounth(pc.GetMonth(LastDate)) + " " + pc.GetYear(LastDate) + GetHour(LastDate));
            }
            else if(DifferenceDays>0)
                Result.Append(DifferenceDays.ToString() + " روز پیش" + " ، " + getDay(pc.GetDayOfWeek(LastDate)) + " " + pc.GetDayOfMonth(LastDate).ToString() + " " + GetMounth(pc.GetMonth(LastDate)) + " " + pc.GetYear(LastDate) + GetHour(LastDate));
            else if(DifferenceDays==0)
                Result.Append(" امروز" + " ، " + getDay(pc.GetDayOfWeek(LastDate)) + " " + pc.GetDayOfMonth(LastDate).ToString() + " " + GetMounth(pc.GetMonth(LastDate)) + " " + pc.GetYear(LastDate) + GetHour(LastDate));

            lblResult.Text = Result.ToString();
        }
کد زیر برای دریافت تعداد اختلاف بین ماه‌ها، از تاریخی گذشته تا تاریخ جاری است:
if(DateTime.Now.Month>LastDate.Month)
                DiffernceMounth = DateTime.Now.Month - LastDate.Month; 
            else
            DiffernceMounth=LastDate.Month-DateTime.Now.Month;
چرا از if استفاده شده است؟ فرض کنید تاریخ امروز 2/12/2015 باشد و تاریخی که قصد تبدیل آن را داریم :10/12/2014 است. تعداد اختلافی که بین تعداد ماه‌ها است 8 ماه است و اگر این بررسی چک کردن بزرگ بودن آن دو انجام نشود، مقدار 8- را بر میگرداند که برای کار ما نادرست است.
نمونه‌ای از این تبدیل :
مطالب
با HttpHandler بیشتر آشنا شوید
در  مقاله قبل توضیح دادیم که وظیفه httphandler رندر و پردازش خروجی یک درخواست هست؛ حالا در این مقاله قصد داریم که مفهوم httphandler را بیشتر بررسی کنیم.

HttpHandler
برای تهیه‌ی یک httphandler، باید کلاسی را بر اساس اینترفیس IHttpHandler پیاده سازی کنیم و بعدا آن را در web.config برنامه معرفی کنیم. برای پیاده سازی این اینترفیس، به یک متد به اسم ProcessRequest با یک پارامتر از نوع HttpContext و یک پراپرتی به اسم IsReusable نیاز داریم که مقدار برگشتی این پراپرتی را false بگذارید؛ بعدا خواهم گفت چرا اینکار را می‌کنیم. نحوه‌ی پیاده‌سازی یک httphandler به شکل زیر است:
public class MyHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {        
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
با استفاده از شیء context می‌توان به دو شیء httpresponse و httprequest دسترسی داشت. تکه کد زیر مثالی است در مورد نحوه‌ی تغییر در محتوای سایت:
public class MyHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        HttpRequest request = context.Request;

        response.Write("Every Page has a some text like this");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
بگذارید همین کد ساده را در وب کانفیگ معرفی کنیم:
<system.web>
  <httpHandlers>
      <add verb="*" path="*.aspx" type="MyHttpHandler"/>
  </httpHandlers>
</system.web>
اگر نسخه IIS شما همانند نسخه‌ی من باشد، نباید هیچ تغییری مشاهده کنید؛ زیرا کد بالا فقط در مورد نسخه‌ی IIS6 صدق می‌کند و برای نسخه‌های IIS 7 به بعد باید به شیوه زیر عمل کنید:
<configuration>
  <system.web>
    <httpHandlers>

  <add name="myhttphandler" verb="*" path="*.aspx" type="MyHttpHandler"/>

    </httpHandlers>
  </system.web>
</configuration>
خروجی نهایی باید تنها این متن باشد: Every Page has a some text like this 
گزینه Type که نام کلاس می‌باشد و اگر کلاس داخل یک فضای نام قرار گرفته باشد، باید اینطور نوشت : namespace.ClassName  
گزینه verb شامل مقادیری چون Get,Post,Head,Putو Delete می‌باشد و httphandler را فقط برای این نوع درخواست‌ها اجرا می‌کند و در صورتیکه بخواهید چندتا از آن‌ها را استفاده کنید، با , از هم جدا می‌شوند. مثلا Get,post و درصورتیکه همه‌ی گزینه‌ها را بخواهید علامت * را میتوان استفاده کرد. 
 گزینه‌ی path این امکان را به شما می‌دهد که مسیر و نوع فایل‌هایی را که قصد دارید روی آن‌ها فقط اجرا شود، مشخص کنید و ما در قطعه کد بالا گفته‌ایم که تنها روی فایل‌هایی با پسوند aspx اجرا شود و چون مسیری هم ذکر نکردیم برای همه‌ی مسیرها قابل اجراست. یکی از مزیت‌های دادن پسوند این است که می‌توانید پسوندهای اختصاصی داشته باشید. مثلا پسوند RSS برای فیدهای وب سایتتان. بسیاری از برنامه نویسان به جای استفاده از صفحات aspx از ashx استفاده می‌کنند که به مراتب سبک‌تر از aspx هست و شامل بخش ui نمی‌شود و نتیجه خروجی آن بر اساس کدی که می‌نویسید مشخص می‌شود که میتواند صفحه متنی یا عکس یا xml یا ... باشد. در اینجا در مورد ساخت صفحات ashx توضیح داده شده است. 

  IHttpHandlerFactory
کار این اینترفیس پیاده سازی یک کلاس است که خروجی آن یک کلاس از نوع IHttpHandler هست. اگر دقت کنید در مثال‌های قبلی ما برای معرفی یک هندلر در وب کانفیگ یک سری path را به آن میدادیم و برای نمونه aspx.* را معرفی می‌کردیم؛ یعنی این هندلر را بر روی همه‌ی فایل‌های aspx اجرا کن و اگر دو یا چند هندلر در وب کانفیگ معرفی کنیم و برای همه مسیر aspx را قرار بدهیم، یعنی همه این هندلرها باید روی صفحات aspx اجرا گردند ولی در httphandlerfactory، ما چند هندلر داریم و میخواهیم فقط یکی از آن‌ها بر روی صفحات aspx انجام بگیرد، پس ما یک هندلرفکتوری را برای صفحات aspx معرفی می‌کنیم و در حین اجرا تصمیم می‌گیریم که کدام هندلر را ارسال کنیم.
اجازه بدهید نوشتن این نوع کلاس را آغاز کنیم،ابتدا دو هندلر به نام‌های httphandler1 و httphandler2 می‌نویسیم :
public class MyHttpHandler1 :IHttpHandler
{
   
    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        response.Write("this is httphandler1");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

public class MyHttpHandler2 : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        response.Write("this is httphandler2");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
سپس کلاس MyFactory را بر اساس اینترفیس IHttpFactory پیاده سازی می‌کنیم و باید دو متد برای آن صدا بزنیم؛ یکی که هندلر انتخابی را بر میگرداند و دیگری هم برای رها کردن یا آزادسازی یک هندلر هست که در این مقاله کاری با آن نداریم. عموما GC دات نت در این زمینه کارآیی خوبی دارد. در قسمت هندلرهای غیرهمزمان به طور مختصر خواهیم گفت که GC چطور آن‌ها را مدیریت می‌کند. کد زیر نمونه کلاسی است که توسط IHttpFactory پیاده سازی شده است:
public class MyFactory : IHttpHandlerFactory
{
    public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTrasnlated)
    {
        
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
        
    }
}
در متد GetHandler چهار آرگومان وجود دارند که به ترتیب برای موارد زیر به کار می‌روند:
 Context یک شی از کلاس httpcontext که دسترسی ما را برای اشیاء سروری چون response,request,session و... فراهم می‌کند.
 RequestType  مشخص می‌کند که درخواست صفحه به چه صورتی است. این گزینه برای مواردی است که verb بیش از یک مورد را حمایت می‌کند. برای مثال دوست دارید یک هندلر را برای درخواست‌های Get ارسال کنید و هندلر دیگر را برای درخواست‌های نوع Post
 URL مسیر مجازی virtual Path صفحه صدا زده شده 
 PathTranslated مسیر فیزیکی صفحه درخواست کننده را ارسال می‌کند. 
متد GetHandler را بدین شکل می‌نویسیم و میخواهیم همه صفحات aspx هندلر شماره یک را انتخاب کنند و صفحات aspx که نامشان با t شروع می‌شوند، هندلر  شماره دو را انتخاب کند:
 public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTrasnlated)
    {
        string handlername = "MyHttpHandler1";
        if(url.Substring(url.LastIndexOf("/")+1).StartsWith("t"))
        {
            handlername = "MyHttpHandler2";
        }

        try
        {
            return (IHttpHandler) Activator.CreateInstance(Type.GetType(handlername));
        }
        catch (Exception e) 
        {
            throw new HttpException("Error: " + handlername, e);
        }
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
        
    }
}
شی Activator که برای ساخت اشیاء با انتخاب بهترین constructor موجود بر اساس یک نوع Type مشخص به کار می‌رود و خروجی Object را می‌گرداند؛ با یک تبدیل ساده، خروجی به قالب اصلی خود باز میگردد. برای مطالعه بیشتر در مورد این کلاس به اینجا و اینجا مراجعه کنید.
نحوه‌ی تعریف factory در وب کانفیگ مانند قبل است و فقط باید در Type به جای نام هندلر نام فکتوری را نوشت. برنامه را اجرا کنید تا نتیجه آن را ببینیم:
تصویر زیر نتیجه صدا زده شدن فایل default.aspx است:

تصویر زیر نتیجه صدا زده شدن فایل Tours_List.aspx است:

AsyncHttpHandlers
برای اینکه کار این اینترفیس را درک کنید بهتر هست اینجا را مطالعه کنید. در اینجا به خوبی تفاوت متدهای همزمان و غیرهمزمان توضیح داده شده است.
متن زیر خلاصه‌ترین و بهترین توضیح برای این پرسش است، چرا غیرهمزمان؟
در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا می‌برند. به این ترتیب worker thread جاری (که تعداد آن‌ها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده می‌شود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت می‌توان با منابع کمتری، درخواست‌های بیشتری را پردازش کرد. 
موقعی که اینترفیس IHttpAsyncHandler را ارث بری کنید (این اینترفیس نیز از IHttpHandler ارث بری کرده است و دو متد اضافه‌تر دارد)، باید دو متد دیگر را نیز پیاده سازی کنید:
 public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object obj)
    {
        
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        
    }
پراپرتی ISResuable هم موقعی که true برگشت بدهد، باعث می‌شود pooling فعال شده و این هندلر در حافظه باقی بماند و تمامی درخواست‌ها از طریق همین یک نمونه اجرا شوند.
به زبان ساده‌تر، این پراپرتی می‌گوید اگر چندین درخواست از طرف کلاینت‌ها برسد، توسط یک نمونه یا instance از هندلر پردازش خواهند شد؛ چون به طور پیش فرض موقعی که تمام درخواست‌های از pipeline بگذرند، هندلر‌ها توسط httpapplication در یک لیست بازیافت قرار گرفته و همه‌ی آن‌ها با null مقداردهی می‌شوند تا از حافظه پاک شوند ولی اگر این پراپرتی true برگرداند، هندلر مربوطه نال نشده و برای پاسخگویی به درخواست‌های بعدی در حافظه خواهد ماند.
مهمترین مزیت این گزینه، این می‌باشد که کاآیی سیستم را بالا می‌برد و اشیا کمتری به GC پاس می‌شوند. ولی یک عیب هم دارد که این تردهایی که ایجاد می‌کند، امنیت کمتری دارند و باید توسط برنامه نویس این امنیت بالاتر رود. این پراپرتی را در مواقعی که با هندلرهای همزمان کار می‌کنید برابر با false بگذارید چون این گزینه بیشتر بر روی هندلرهای غیرهمزمان اثر دارد و هم اینکه بعضی‌ها توصیه می‌کنند که false بگذارید چون GC مدیریت خوبی در مورد هندلرها دارد و هم این که ارزش یافتن باگ در کد را ندارد.
بر میگردیم سراغ کد نویسی هندلر غیر همزمان. در آخرین قطعه کد نوشته شده، ما دو متد دیگر را پیاده سازی کردیم که یکی از آن‌ها BeginProcessRequest  است و خروجی آن کلاسی است که از اینترفیس IAsyncResult  ارث بری کرده است. پس یک کلاس با ارث بری از این اینترفیس می‌نویسیم و در این کلاس نیاز است که 4 پراپرتی را پیاده سازی کنیم که این کلاس به شکل زیر در خواهد آمد:
public class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }
}
متدهای private اجباری نیستند؛ ولی برای ذخیره مقادیر get و set نیاز است. همانطور که از اسامی آن‌ها پیداست مشخص است که برای چه کاری ساخته شده اند.
خب اجازه بدهید یک تابع سازنده به آن برای مقداردهی اولیه این متغیرهای خصوصی داشته باشیم:
   public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }
همانطور که می‌بینید موارد موجود در متد BeginProcessRequest را تحویل می‌گیریم تا اطلاعات درخواستی مربوطه را داشته باشیم و مقدار _Completed را هم برابر با false قرار می‌دهیم. سپس نوبت این می‌رسد که ما درخواست را در صف pool قرار دهیم. برای همین تکه کد زیر را اضافه می‌کنیم: 
   public void StartAsyncWork()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask),null);
    }
با اضافه شدن درخواست به صف، هر موقع درخواست‌های قبلی تمام شوند و callback خودشان را ارسال کنند، نوبت درخواست‌های جدیدتر هم میرسد. StartAsyncTask هم متدی است که وظیفه‌ی اصلی پردازش درخواست را به دوش دارد و موقعی که نوبت درخواست برسد، کدهای این متد اجرا می‌گردد که ما در اینجا مانند مثال اول روی صفحه چیزی نوشتیم:
 private void StartAsyncTask(Object workItemState)
    {

        _context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");

        _context.Response.Write("Hello World from Async Handler!");
        _completed = true;
        _callback(this);
    }
دو خط اول اطلاعات را چاپ کرده و در خط سوم متغیر _completed را true کرده و در آخر این درخواست را فراخوانی مجدد می‌کنیم تا بگوییم که کار این درخواست پایان یافته‌است؛ پس این درخواست را از صف بیرون بکش و درخواست بعدی را اجرا کن.
نهایتا کل این کلاس را در متد BeginProcessRequest  صدا بزنید:
context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
        AsynchOperation asynch = new AsynchOperation(callback, context, obj);
        asynch.StartAsyncWork();
        return asynch;
کل کد مربوطه : (توجه:کدها از داخل سایت msdn برداشته شده است و اکثر کدهای موجود در نت هم به همین قالب می‌نویسند)
public class MyHttpHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object obj)
    {
        context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
        AsynchOperation asynch = new AsynchOperation(callback, context, obj);
        asynch.StartAsyncWork();
        return asynch;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        
    }
    public void ProcessRequest(HttpContext context)
    {
       throw new InvalidOperationException(); 

    }

    public bool IsReusable
    {
        get { return false; }
    }
}

public class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }

    public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }


    public void StartAsyncWork()
    {
        
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask),null);

    }
    private void StartAsyncTask(Object workItemState)
    {

        _context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");

        _context.Response.Write("Hello World from Async Handler!");
        _completed = true;
        _callback(this);
    }

آشنایی با فایل ASHX
در مطالب بالاتر به فایل‌های Ashx اشاره کردیم. این فایل به نام Generic Web Handler شناخته می‌شوند و می‌توانید با Add New Item این نوع فایل‌ها را اضافه کنید. این فایل شامل هیچ UI ایی نمی‌باشد و فقط شامل بخش کد می‌باشد. برای همین نسبت به aspx سبک‌تر بوده و شامل یک directive به اسم  WebHandler@ است.
مایکروسافت در MSDN نوشته است که httphandler‌ها در واقع فرآیندهایی هستند (به این فرایندها بیشتر End Point می‌گویند) که در پاسخ به درخواست‌های رسیده شده توسط asp.net application اجرا می‌شوند و بیشترین درخواست هایی هم که می‌رسد از نوع صفحات Aspx می‌باشد و موقعی که کاربری درخواست صفحه‌ی aspx می‌کند هندلرهای مربوط به page اجرا می‌شوند.
در متن بالا به خوبی روشن هست که ashx به دلیل نداشتن UI، تعداد کمتری از handlerها را در مسیر Pipeline قرار می‌دهند و اجرای آن‌ها سریعتر است. غیر از این دو هندلر aspx و ashx، هندلر توکار دیگری چون asmx که مختص وب سرویس هست و axd مربوط به اعمال trace نیز وجود دارند.

در این لینک که در بالاتر هم درج شده بود یک نمونه هندلر برای نمایش تصویر نوشته است. اگر تصاویرتان را بدین صورت اجرا کنید می‌توان جلوی درخواست‌های رسیده از وب سایت‌های دیگر را سد کرد. برای مثال یک نفر مطالب شما را کپی می‌کند و در داخل وبلاگ یا وب سایتش می‌گذارد و شما در اینجا درخواست‌های رسیده خارج از وب سایت خود را لغو خواهید کرد و تصاویر کپی شده نمایش داده نخواهند شد.
مطالب دوره‌ها
شی گرایی در #F
برنامه نویسی شی گرای سومین نسل از الگوهای اصلی برنامه نویسی است. در توضیحات فصل اول گفته شد که #F یک زبان تابع گرا است ولی این بدان معنی نیست که #F از مفاهیمی نظیر کلاس و یا interface پشتیبانی نکند. برعکس در #F امکان تعریف کلاس و interface و هم چنین پیاده سازی مفاهیم شی گرایی وجود دارد.

*با توجه به این موضوع که فرض است دوستان با مفاهیم شی گرایی آشنایی دارند از توضیح و تشریح این مفاهیم خودداری می‌کنم.

Classes
کلاس چارچوبی از اشیا است برای نگهداری خواص(Properties) و رفتار ها(Methods) و رخدادها(Events). کلاس پایه ای‌ترین مفهوم در برنامه نویسی شی گراست. ساختار کلی تعربف کلاس در #F به صورت زیر است:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
   [ class ]
     [ inherit base-type-name(base-constructor-args) ]
     [ let-bindings ]
     [ do-bindings ]
     member-list
      ...
   [ end ]

type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
همان طور که در ساختار بالا می‌بینید مفاهیم access-modifier و inherit و constructor هم در #F وجود دارد.

انواع access-modifier در #F
  • public : دسترسی برای تمام فراخوان‌ها امکان پذیر است
  • internal : دسترسی برای تمام فراخوان هایی که در همین assembly هستند امکان پذیر است
  • private : دسترسی فقط برای فراخوان‌های موجود در همین ماژول امکان پذیر است

نکته : protected access modifier در #F پشتیبانی نمی‌شود.

مثالی از تعریف کلاس:

type Account(number : int, name : string) = class
    let mutable amount = 0m
   
end
کلاس بالا دارای یک سازنده است که دو پارامتر ورودی می‌گیرد. کلمه end به معنای انتهای کلاس است. برای استفاده کلاس باید به صورت زیر عمل کنید:
let myAccount = new Account(123456, "Masoud")
توابع و خواص در کلاس ها
برای تعریف خاصیت در #F باید از کلمه کلیدی member استفاده کنید. در مثال بعدی برای کلاس بالا تابع و خاصیت تعریف خواهیم کرد.
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
کلاس بالا دارای سه خاصیت به نام‌های Number و Name و Amount است و دو تابع به نام‌های Deposit و Withdraw دارد. اما x استفاده شده قبل از هر member به معنی this در #C  است. در #F شما برای اشاره به شناسه‌های یک محدوده خودتون باید یک نام رو برای اشاره گر مربوطه تعیین کنید.
open System
 
type Account(number : int, name: string) = class
    let mutable amount = 0m
 
    member x.Number = number
    member x.Name= name
    member x.Amount = amount
 
    member x.Deposit(value) = amount <- amount + value
    member x.Withdraw(value) = amount <- amount - value
end
 let masoud= new Account(12345, "Masoud") let saeed = new Account(67890, "Saeed") let transfer amount (source : Account) (target : Account) = source.Withdraw amount target.Deposit amount let printAccount (x : Account) = printfn "x.Number: %i, x.Name: %s, x.Amount: %M" x.Number x.Name x.Amount let main() = let printAccounts() = [masoud; saeed] |> Seq.iter printAccount printfn "\nInializing account" homer.Deposit 50M marge.Deposit 100M printAccounts() printfn "\nTransferring $30 from Masoud to Saeed" transfer 30M masoud saeed
 printAccounts() printfn "\nTransferring $75 from Saeed to Masoud" transfer 75M saeed masoud printAccounts() main()
استفاده از کلمه do
در #F زمانی که قصد داشته باشیم در بعد از وهله سازی از کلاس و فراخوانی سازنده، عملیات خاصی انجام شود(مثل انجام برخی عملیات متداول در سازنده‌های کلاس‌های دات نت) باید از کلمه کلیدی do به همراه یک بلاک از کد استفاده کنیم.
open System
open System.Net
 
type Stock(symbol : string) = class

    let mutable _symbol = String.Empty
    do
     //کد مورد نظر در این جا نوشته  میشود
end
یک مثال در این زمینه:

open System

type MyType(a:int, b:int) as this =
    inherit Object()
    let x = 2*a
    let y = 2*b
    do printfn "Initializing object %d %d %d %d %d %d"
               a b x y (this.Prop1) (this.Prop2)
    static do printfn "Initializing MyType." 
    member this.Prop1 = 4*x
    member this.Prop2 = 4*y
    override this.ToString() = System.String.Format("{0} {1}", this.Prop1, this.Prop2)

let obj1 = new MyType(1, 2)
در مثال بالا دو عبارت do  یکی به صورت static و دیگری به صورت غیر static تعریف شده اند. استفاده از do  به صورت غیر static این امکان را به ما می‌دهد که بتوانیم به تمام شناسه‌ها و توابع تعریف شده در کلاس استفاده کنیم ولی do به صورت static فقط به خواص و توابع از نوع static در کلاس دسترسی دارد.
خروجی مثال بالا:
Initializing MyType.
Initializing object 1 2 2 4 8 16
خواص static:
برای تعریف خواص به صورت استاتیک مانند #C از کلمه کلیدی static استفاده کنید.مثالی در این زمینه:
type SomeClass(prop : int) = class
    member x.Prop = prop
    static member SomeStaticMethod = "This is a static method"
end
SomeStaticMethod به صورت استاتیک تعریف شده در حالی که x.Prop به صورت غیر استاتیک. دسترسی به متد‌ها یا خواص static باید بدون وهله سازی از کلاس انجام بگیرد در غیر این صورت با خطای کامپایلر روبرو خواهید شد.
let instance = new SomeClass(5);;
instance.SomeStaticMethod;; 

output:
stdin(81,1): error FS0191: property 'SomeStaticMethod' is static.
روش استفاده درست:
SomeClass.SomeStaticMethod;; (* invoking static method *)
متد‌های get , set در خاصیت ها:
همانند #C و سایر زبان‌های دات نت امکان تعریف متد‌های get و set برای خاصیت‌های یک کلاس وجود دارد.
ساختار کلی:
 member alias.PropertyName
        with get() = some-value
        and set(value) = some-assignment
مثالی در این زمینه:
type MyClass() = class
   let mutable num = 0 
    member x.Num
        with get() = num
        and set(value) = num <- value
end;;
کد متناظر در #C:
public int Num
{
   get{return num;}
   set{num=value;}
}
یا به صورت:
type MyClass() = class
    let mutable num = 0
 
    member x.Num
        with get() = num
        and set(value) =
            if value > 10 || value < 0 then
                raise (new Exception("Values must be between 0 and 10"))
            else
                num <- value
end

Interface ها
اینترفیس به تمامی خواص و توابع عمومی اشئایی که آن را پیاده سازی کرده اند اشاره می‌کند. (توضیحات بیشتر (^ ) و (^ ))ساختار کلی برای تعریف آن به صورت زیر است:
type type-name = 
   interface
       inherits-decl 
       member-defns 
   end
مثال:
type IPrintable =
   abstract member Print : unit -> unit
استفاده از حرف I برای شروع نام اینترفیس طبق قوانین تعریف شده (اختیاری) برای نام گذاری است.
نکته: در هنگام تعریف توابع و خاصیت در interface‌ها باید از کلمه abstract استفاده کنیم. هر کلاسی که از یک یا چند تا اینترفیس ارث ببرد باید تمام خواص و توابع اینتریس‌ها را پیاده سازی کند. در مثال بعدی کلاس SomeClass1 اینترفیس بالا را پیاده سازی می‌کند. دقت کنید که کلمه this توسط من به عنوان اشاره گر به اشیای کلاس تعیین شده و شما می‌تونید از هر کلمه یا حرف دیگری استفاده کنید.
type SomeClass1(x: int, y: float) =
   interface IPrintable with 
      member this.Print() = printfn "%d %f" x y
نکته مهم: اگر قصد فراخوانی متد Print را در کلاس بالا دارید نمی‌تونید به صورت مستقیم متد بالا را فراخوانی کنید. بلکه حتما باید کلاس به اینترفیس مربوطه cast شود.
روش نادرست:
let instance = new SomeClass1(10,20)
instance.Print//فراخوانی این متد باعث ایجاد خطای کامپایلری می‌شود.
روش درست:
let instance = new SomeClass1(10,20) 
let instanceCast = instance :> IPrintable// استفاده از (<:)  برای عملیات تبدیل کلاس به اینترفیس
instanceCast.Print
برای عملیات cast ازاستفاده کنید.
در مثال بعدی کلاسی خواهیم داشت که از سه اینترفیس ارث می‌برد. در نتیجه باید تمام متد‌های هر سه اینترفیس را پیاده سازی کند.
type Interface1 =
    abstract member Method1 : int -> int

type Interface2 =
    abstract member Method2 : int -> int

type Interface3 =
    inherit Interface1
    inherit Interface2
    abstract member Method3 : int -> int

type MyClass() =
    interface Interface3 with 
        member this.Method1(n) = 2 * n
        member this.Method2(n) = n + 100
        member this.Method3(n) = n / 10
فراخوانی این متد‌ها نیز به صورت زیر خواهد بود:
let instance = new MyClass()
let instanceToCast = instance :> Interface3
instanceToCast.Method3 10
کلاس‌های Abstract
#F از کلاس‌های abstract هم پشتیبانی می‌کند. اگر با کلاس‌های abstract در #C آشنایی ندارید می‌تونید مطالب مورد نظر رو در  (^ ) و (^ ) مطالعه کنید. به صورت خلاصه کلاس‌های abstract به عنوان کلاس‌های پایه در برنامه نویسی شی گرا استفاده می‌شوند. این کلاس‌ها دارای خواص و متد‌های پیاده سازی شده و نشده هستند. خواص و متد هایی که در کلاس پایه abstract پیاده سازی نشده اند باید توسط کلاس هایی که از این کلاس پایه ارث می‌برند حتما پیاده سازی شوند.
ساختار کلی تعریف کلاس‌های abstract:
[<AbstractClass>]
type [ accessibility-modifier ] abstract-class-name =
    [ inherit base-class-or-interface-name ]
    [ abstract-member-declarations-and-member-definitions ]

    abstract member member-name : type-signature
در #F برای این که مشخص کنیم که یک کلاس abstract است حتما باید [<AbstractClass>] در بالای کلاس تعریف شود.
[<AbstractClass>]
type Shape(x0 : float, y0 : float) =
    let mutable x, y = x0, y0
    let mutable rotAngle = 0.0

    abstract Area : float with get
    abstract Perimeter : float  with get
    abstract Name : string with get
کلاس بالا تعریفی از کلاس abstract است که سه خصوصیت abstract دارد (برای تعیین خصوصیت‌ها و متد هایی که در کلاس پایه پیاده سازی نمی‌شوند از کلمه کلیدی abstract در هنگام تعریف آن‌ها استفاده می‌کنیم). حال دو کلاس ایجاد می‌کنیم که این کلاس پایه را پیاده سازی کنند.

#1 کلاس اول
type Square(x, y,SideLength) =
    inherit Shape(x, y)
  override this.Area = this.SideLength * this.SideLength override this.Perimeter = this.SideLength * 4. override this.Name = "Square"
#2 کلاس دوم
type Circle(x, y, radius) =
    inherit Shape(x, y)
 let PI = 3.141592654 member this.Radius = radius override this.Area = PI * this.Radius * this.Radius override this.Perimeter = 2. * PI * this.Radius
Structures
structure‌ها در #F دقیقا معال struct در #C هستند. توضیحات بیشتر درباره struct در #C (^ ) و (^ )). اما به طور خلاصه باید ذکر کنم که strucure‌ها تقریبا دارای مفهوم کلاس هستند با اندکی تفاوت که شامل موارد زیر است:
  • structure‌ها از نوع مقداری هستند و این بدین معنی است مستقیما درون پشته ذخیره می‌شوند.
  • ارجاع به structure‌ها از نوع ارجاع با مقدار است بر خلاف کلاس‌ها که از نوع ارجاع به منبع هستند.(^ )
  • structure‌ها دارای خواص ارث بری نیستند.
  • عموما از structure برای ذخیره مجموعه ای از داده‌ها با حجم و اندازه کم استفاده می‌شود.

ساختار کلی تعریف structure

[ attributes ]
type [accessibility-modifier] type-name =
   struct
      type-definition-elements
   end

//یا به صورت زیر

[ attributes ]
[<StructAttribute>]
type [accessibility-modifier] type-name =
   type-definition-elements
یک نکته مهم هنگام کار با struct‌ها در #F این است که امکان استفاده از let و Binding در struct‌ها وجود ندارد. به جای آن باید از val استفاده کنید.
type Point3D =
   struct 
      val x: float
      val y: float
      val z: float
   end
تفاوت اصلی بین val و let در این است که هنگام تعریف شناسه با val امکان مقدار دهی اولیه به شناسه وجود ندارد. در مثال بالا مقادیر برای x و y و z برابر 0.0 است که توسط کامپایلر انجام می‌شود. در ادامه یک struct به همراه سازنده تعریف می‌کنیم:
type Point2D =
   struct 
      val X: float
      val Y: float
      new(x: float, y: float) = { X = x; Y = y }
   end
توسط سازنده struct بالا مقادیر اولیه x و y دریافت می‌شود به متغیر‌های متناظر انتساب می‌شود.

  در پایان یک مثال مشترک رو در #C و #F پیاده سازی می‌کنیم:


نظرات مطالب
پیاده سازی Option یا Maybe در #C
منظور من رو متوجه نشدید. بذارید با کد توضیح بدم:
میخواهیم طبق هدف مقاله، این تکه کد را اصلاح کنیم.
public ActionResult Details(int id)
        {
            var user=_userService.GetById(3); // این متد ممکن است مقداری برگرداند و یا مقدار نال برگرداند
            if( user == null)
                 return HttpNotFound();
            return View(user);
        }
راهکار ارائه شده:
public ActionResult Details(int id)
        {
            var user = _userService
                            .GetById(3)
                            .DefaultIfEmpty(new User())
                            .Single();
            return View(user);
        }
پر واضح است خروجی این دو متد با هم یکسان نیستند.
راه حل ارائه شده کامل نیست و با تغییر صورت مساله، به جواب دیگری میرسد.
باید به کدی مثل این برسیم:
public ActionResult Details(int id)
{
    return 
        Search<ActionResult>(id)
        .OnExistValue(View("Details"))
        .OnNotExistValue(new HttpNotFoundResult())
        .ToValue();
}
برای نیل به این هدف:
public class Maybe<T, TResult> : IEnumerable<T>
    {
        private readonly T[] _data;
        private readonly TResult _result;

        private Maybe(T[] data)
        {
            _data = data;
        }

        private Maybe(TResult result)
        {
            _result = result;
        }

        public TResult ToValue() => _result;
        public Maybe<T, TResult> OnExistValue(TResult result) => _data.Any() ? new Maybe<T, TResult>(result) : this;

        public Maybe<T, TResult> OnNotExistValue(TResult result) => _result == null ? new Maybe<T, TResult>(result) : this; 

        public static Maybe<T, TResult> Create(T element) => new Maybe<T, TResult>(new[] {element});

    public static Maybe<T, TResult> CreateEmpty() => new Maybe<T, TResult>(new T[0]);

    public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _data).GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
و متد جستجو نیز ایچنین تغییر خواهد کرد:
public Maybe<User, TResult> Search<TResult>(int id)
{
    var lst = new User[] {};

    var r = lst.Where(x => x.Id == id).ToList();
    return r.Any() ? Maybe<User, TResult>.Create(r[0]) : Maybe<User, TResult>.CreateEmpty();
}
یکی از راه حل‌ها میتواند این کدها باشند.
مطالب
جستجوی یک property در model یا object

گاهی نیاز هست در بین property‌های model‌های مختلفی که از یک مدل base ارث بری کرده اند، جستجویی را انجام دهیم؛ همانند مدل زیر:

    #region my model

    [KnownType(typeof(model1))]
    [KnownType(typeof(model2))]
    [KnownType(typeof(model3))]
    public class TaskGroupObjects : List<IBaseObject>
    {
        #region sortFields
        class compare : IComparer<IBaseObject>
        {
            public int Compare(IBaseObjectfield1, IBaseObjectfield2)
            {
                return field1.Order - field2.Order;
            }
        }

        public void sortByOrder()
        {
            Sort(new compare());
        }
        #endregion
    }
اینکه model، هیچ property ای که منجر به مشخص شدن type آن بشود را نداشته باشد هم آزار دهنده هست. متد زیر مدل را بررسی و در بین property‌های مدل پاس داده شده اگر property مد نظر ما را پیدا کند، مقدار true را برمیگرداند:
public static bool HasProperty(this object your model, string propertyName)
{
    return obj.GetType().GetProperty(propertyName) != null;
}
مطالب
تکمیل کلاس DelegateCommand

مدت‌ها از کلاس DelegateCommand معرفی شده در این آدرس استفاده می‌کردم. این کلاس یک مشکل جزئی دارد و آن هم عدم بررسی مجدد قسمت canExecute به صورت خودکار هست.

خلاصه‌ای برای کسانی که بار اول هست با این مباحث برخورد می‌کنند؛ یا MVVM به زبان بسیار ساده:

در برنامه نویسی متداول سیستم مایکروسافتی، در هر سیستمی که ایجاد کرده و در هر فناوری که ارائه داده از زمان VB6 تا امروز، شما روی یک دکمه مثلا دوبار کلیک می‌کنید و در فایل اصطلاحا code behind این فرم و در روال رخدادگردان آن شروع به کد نویسی خواهید کرد. این مورد تقریبا در همه جا صادق است؛ از WinForms تا WPF تا Silverlight تا حتی ASP.NET Webforms . به عمد هم این طراحی صورت گرفته تا برنامه نویس‌ها در این محیط‌ها زیاد احساس غریبی نکنند. اما این روش یک مشکل مهم دارد و آن هم «توهم» جداسازی رابط کاربر از کدهای برنامه است. به ظاهر یک فایل فرم وجود دارد و یک فایل جدای code behind ؛ اما در عمل هر دوی این‌ها یک partial class یا به عبارتی «یک کلاس» بیشتر نیستند. «فکر می‌کنیم» که از هم جدا شدند اما واقعا یکی هستند. شما در code behind صفحه به صورت مستقیم با عناصر رابط کاربری سروکار دارید و کدهای شما به این عناصر گره خورده‌اند.
شاید بپرسید که چه اهمیتی دارد؟
مشکل اول: امکان نوشتن آزمون‌ها واحد برای این متدها وجود ندارد یا بسیار سخت است. این متدها فقط با وجود فرم و رابط کاربری متناظر با آن‌ها هست که معنا پیدا می‌کنند و تک تک عناصر آن‌ها وهله سازی می‌شوند.
مشکل دوم: کد نوشته فقط برای همین فرم جاری آن قابل استفاده است؛ چون به صورت صریح به عناصر موجود در فرم اشاره می‌کند. نمی‌تونید این فایل code behind رو بردارید بدون هیچ تغییری برای فرم دیگری استفاده کنید.
مشکل سوم: نمی‌تونید طراحی فرم رو بدید به یک نفر، کد نویسی اون رو به شخصی دیگر. چون ایندو لازم و ملزوم یکدیگرند.

این سیستم کد نویسی دهه 90 است.
چند سالی است که طراحان سعی کرده‌اند این سیستم رو دور بزنند و روش‌هایی رو ارائه بدن که در آن‌ها فرم‌های برنامه و فایل‌های پیاده سازی کننده‌ی منطق آن هیچگونه ارتباط مستقیمی باهم نداشته باشند؛ به هم گره نخورده باشند؛ ارجاعی به هیچیک از عناصر بصری فرم را در خود نداشته باشند. به همین دلیل ASP.NET MVC به وجود آمده و در همان سال‌ها مثلا MVVM .

سؤال:
الان که رابط کاربری از فایل پیاده سازی کننده منطق آن جدا شده و دیگر Code behind هم نیست (همان partial class های متداول)، این فایل‌ها چطور متوجه می‌شوند که مثلا روی یک فرم، شیءایی قرار گرفته؟ از کجا متوجه خواهند شد که روی دکمه‌ای کلیک شده؟ این‌ها که ارجاعی از فرم را در درون خود ندارند.
در الگوی MVVM این سیم کشی توسط امکانات قوی Binding موجود در WPF میسر می‌شود. در ASP.NET MVC چیزی شبیه به آن به نام Model binder و همان مکانیزم‌های استاندارد HTTP این کار رو می‌کنه. در MVVM شما بجای code behind خواهید داشت ViewModel (اسم جدید آن). در ASP.NET MVC این اسم شده Controller. بنابراین اگر این اسامی رو شنیدید زیاد تعجب نکنید. این‌ها همان Code behind قدیمی هستند اما ... بدون داشتن ارجاعی از رابط کاربری در خود که ... اطلاعات موجود در فرم به نحوی به آن‌ها Bind و ارسال می‌شوند.
این سیم کشی‌ها هم نامرئی هستند. یعنی فایل ViewModel یا فایل Controller نمی‌دونند که دقیقا از چه کنترلی در چه فرمی این اطلاعات دریافت شده.
این ایده هم جدید نیست. شاید بد نباشه به دوران طلایی Win32 برگردیم. همان توابع معروف PostMessage و SendMessage را به خاطر دارید؟ شما در یک ترد می‌تونید با مثلا PostMessage شیءایی رو به یک فرم که در حال گوش فرا دادن به تغییرات است ارسال کنید (این سیم کشی هم نامرئی است). بنابراین پیاده سازی این الگوها حتی در Win32 و کلیه فریم ورک‌های ساخته شده بر پایه آن‌ها مانند VCL ، VB6 ، WinForms و غیره ... «از روز اول» وجود داشته و می‌تونستند بعد از 10 سال نیان بگن که اون روش‌های RAD ایی رو که ما پیشنهاد دادیم، می‌شد خیلی بهتر از همان ابتدا، طور دیگری پیاده سازی بشه.

ادامه بحث!
این سیم کشی یا اصطلاحا Binding ، در مورد رخدادها هم در WPF وجود داره و اینبار به نام Commands معرفی شده‌است. به این معنا که بجای اینکه بنویسید:
<Button  Click="btnClick_Event">Last</Button>

بنویسید:
<Button Command="{Binding GoLast}">Last</Button>

حالا باید مکانیزمی وجود داشته باشه تا این پیغام رو به ViewModel برنامه برساند. اینکار با پیاده سازی اینترفیس ICommand قابل انجام است که معرفی یک کلاس عمومی از پیاده سازی آن‌را در ابتدای بحث مشاهده نمودید.
در یک DelegateCommand،‌ توسط متد منتسب به executeAction، مشخص خواهیم کرد که اگر این سیم کشی برقرار شد (که ما دقیقا نمی‌دانیم و نمی‌خواهیم که بدانیم از کجا و کدام فرم دقیقا)، لطفا این اعمال را انجام بده و توسط متد منتسب به canExecute به سیستم Binding خواهیم گفت که آیا مجاز هستی این اعمال را انجام دهی یا خیر. اگر این متد false برگرداند، مثلا دکمه یاد شده به صورت خودکار غیرفعال می‌شود.
اما مشکل کلاس DelegateCommand ذکر شده هم دقیقا همینجا است. این دکمه تا ابد غیرفعال خواهد ماند. در WPF کلاسی وجود دارد به نام CommandManager که حاوی متدی استاتیکی است به نام InvalidateRequerySuggested. اگر این متد به صورت دستی فراخوانی شود، یکبار دیگر کلیه متدهای منتسب به تمام canExecute های تعریف شده، به صورت خودکار اجرا می‌شوند و اینجا است که می‌توان دکمه‌ای را که باید مجددا بر اساس شرایط جاری تغییر وضعیت پیدا کند، فعال کرد. بنابراین فراخوانی متد InvalidateRequerySuggested یک راه حل کلی رفع نقیصه‌ی ذکر شده است.
راه حل دومی هم برای حل این مشکل وجود دارد. می‌توان از رخدادگردان CommandManager.RequerySuggested استفاده کرد. روال منتسب به این رخدادگردان هر زمانی که احساس کند تغییری در UI رخ داده، فراخوانی می‌شود. بنابراین پیاده سازی بهبود یافته کلاس DelegateCommand به صورت زیر خواهد بود:

using System;
using System.Windows.Input;

namespace MvvmHelpers
{
// Ref.
// - http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
// - http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/
public class DelegateCommand<T> : ICommand
{
readonly Func<T, bool> _canExecute;
bool _canExecuteCache;
readonly Action<T> _executeAction;

public DelegateCommand(Action<T> executeAction, Func<T, bool> canExecute = null)
{
if (executeAction == null)
throw new ArgumentNullException("executeAction");

_executeAction = executeAction;
_canExecute = canExecute;
}

public event EventHandler CanExecuteChanged
{
add { if (_canExecute != null) CommandManager.RequerySuggested += value; }
remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; }
}

public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}

public void Execute(object parameter)
{
_executeAction((T)parameter);
}
}
}

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

public DelegateCommand<string> GoLast { set; get; }

//in ctor
GoLast = new DelegateCommand<string>(goLast, canGoLast);

private bool canGoLast(string data)
{
//ex.
return ListViewGuiData.CurrentPage != ListViewGuiData.TotalPage - 1;
}

private void goLast(string data)
{
//do something
}

مزیت کلاس DelegateCommand جدید هم این است که مثلا متد canGoLast فوق، به صورت خودکار با به روز رسانی UI ، فراخوانی و تعیین اعتبار مجدد می‌شود.


نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API
با توجه به مطالب مطرح شده در متن فوق و  نحوه استفاده از ViewModel در ASP.NET MVC و همچنین توصیه‌هایی که در رابطه با آدرس‌دهی صحیح WebApiها وجود دارد (استفاده از اسم جمع، استفاده از اسم به جای فعل و ...)، در رابطه با آدرس‌دهی صحیح برای تامین اطلاعات مورد نیاز Viewها  (در حالت ویرایش یا افزودن)، در سمت کلاینت که استفاده کننده آن می‌تواند یک کامپوننت Angular یا هر نوع دیگری باشد آیا دوستان نظر و Best Practice دارند؟
به طور مثال برای ویرایش و افزودن یک محصول به صورت زیر عمل می‌کنیم:
   //ViewModels

    public class CustomListItem
    {
        public int Id { get; set; }
        public string Text { get; set; }
    }

    public class ProductAddGetViewModel
    {
        public IEnumerable<CustomListItem> Categories { get; set; }
        public IEnumerable<CustomListItem> Groups { get; set; }
    }

    public class ProductAddViewModel
    {
        public string Name { get; set; }
        public bool IsActive { get; set; }

        public int CategoryId { get; set; }
        public int GroupId { get; set; }
    }

    public class ProductEditGetViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public IEnumerable<CustomListItem> Categories { get; set; }
        public IEnumerable<CustomListItem> Groups { get; set; }
    }

    public class ProductEditViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public int GroupId { get; set; }
    }
و
    // ProductsController - ApiControler

    // GET: api/products/views/add
    [HttpGet("views/add")]
    public async Task<IActionResult> GetAdd()
    {
        ProductAddGetViewModel model = await _productService.GetAddModelAsync();
        return Ok(model)
    }

    // POST: api/products
    [HttpPost]
    public async Task<IActionResult> Add(ProductAddViewModel model)
    {
        ...
    }


    // GET: api/products/5/views/edit
    [HttpGet("{id}/views/edit")]
    public async Task<IActionResult> GetEdit(int id)
    {
        ProductEditGetViewModel model = await _productService.GetEditModelAsync(id);
        return Ok(model)
    }

    // PUT: api/products/5
    [HttpPut("{id}")]
    public async Task<IActionResult> Edit(int id, ProductEditViewModel model)
    {
        ...
    }
با توجه به اینکه حالت فوق، احتمالاً دو متد از چند متد مورد استفاده می‌باشد، آیا دوستان درباره متدهای GetAdd , GetEdit و همچنین آدرس‌دهی صحیح این نوع متدها که قرار است از سمت کلاینت فراخوانی شود نظری دارند؟
پ.ن: درباره نامگذاری بهتر ViewModelها هم اگر نظری هست ممنون میشم بیان شود.