مطالب
Long Polling در WCF
به صورت پیش فرض سرویس‌های WCF به صورت Sync اجرا خواهند شد، یعنی هر گاه درخواستی از سمت کلاینت به سرور ارسال شود سرور بعد از پردازش درخواست پاسخ مورد نظر را به کلاینت باز می‌گرداند. اما حالتی را در نظر بگیرید که بعد از دریافت Request از کلاینت بنا به دلایلی امکان پاسخ گویی سمت سرور در آن لحظه وجود ندارد. خوب چه اتفاقی خواهد افتاد؟
در این حالت thread جاری سمت کلاینت نیز در حالت wait است و برنامه سمت کلاینت از کار می‌افتد تا زمانی که پاسخ از سرور دریافت نماید. اما در WCF به صورت پیش فرض هر درخواست ارسالی باید در طی یک دقیقه در اختیار سرور قرار گیرد و سرور نیز باید در طی یک دقیقه پاسخ مورد نظر را برگرداند(مقادیر خواص SendTimeout و ReceiveTimeout برای مدیریت این موارد به کار می‌روند). افزایش مقادیر این خواص کمک خاصی به این حالت نمی‌کند زیرا هم چنان کلاینت در حالت wait است و سرور نیز پاسخ خاصی ارسال نمی‌کند. حتی اگر کل عملیات را به صورت Async پیاده سازی نماییم باز ممکن است بعد از منقضی شدن زمان پردازش با یک TimeoutException برنامه از کار بیفتد. برای حل اینگونه موارد پیاده سازی سرویس‌ها به صورت Long Polling به ما کمک خوبی خواهد کرد.
حال سناریو زیر را در نظر بگیرید:
سمت سرور:
»یک درخواست دریافت می‌شود؛
»سرور در حالت wait (البته توسط یک thread دیگر) منتظر تامین منابع برای پاسخ به کلاینت است؛
»در نهایت پاسخ مورد نظر ارسال خواهد شد.
سمت کلاینت:
»درخواست مورد نظر به سرور ارسال می‌شود؛
»کلاینت منتظر پاسخ از سمت سرور است(البته توسط یک Thread دیگر)؛
»اگر در حین انتظار برای پاسخ از سمت سرور، با یک TimeoutException روبرو شدیم به جای توقف برنامه و نمایش پیغام خطای  Server is not available، باید عملیات به صورت خودکار restart شود.
»در نهایت پاسخ مورد نظر دریافت خواهد شد.
پیاده سازی این سناریو در WCF کار پیچیده ای نیست. بدین منظور می‌توانید از کلاس زیر استفاده کنید( لینک دانلود ). سورس آن به صورت زیر است:
    public abstract class LongPollingAsyncResult<TResult> : IAsyncResult where TResult : class
    {
        #region - Fields -

        private AsyncCallback _callback;
        private TimeSpan _timoutSpan;
        private TimeSpan _intervalWaitSpan;

        #endregion

        #region - Properties -
        public Exception Exception { get; private set; }
    
        public TResult Result { get; private set; }
     
        public object SyncRoot { get; private set; }

        #endregion

        #region - Ctor -
      
        public LongPollingAsyncResult(AsyncCallback callback, object asyncState, int timeoutSeconds = 300, int intervalWaitMilliseconds = 500)
        {
            SyncRoot = new object();
            _callback = callback;
            AsyncState = asyncState;
            AsyncWaitHandle = new ManualResetEvent(IsCompleted);
            _timoutSpan = TimeSpan.FromSeconds(timeoutSeconds);
            _intervalWaitSpan = TimeSpan.FromMilliseconds(intervalWaitMilliseconds);

            ThreadPool.QueueUserWorkItem(new WaitCallback(LoopWithIntervalAndTimeout));
        }

        #endregion

        #region - Private Helper Methods -

        private void LoopWithIntervalAndTimeout(object input)
        {
            try
            {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                while (!IsCompleted)
                {
                    if (stopwatch.Elapsed > _timoutSpan)
                        throw new TimeoutException();
                    
                    DoWork();

                    if (!IsCompleted)
                        Thread.Sleep(_intervalWaitSpan);
                }
            }
            catch (Exception e)
            {
                Complete(null, e);
            }
        }

        #endregion

        #region - Protected/Abstract Methods -
        protected void Complete(TResult result, Exception e = null, bool completedSynchronously = false)
        {
            lock (SyncRoot)
            {
                CompletedSynchronously = completedSynchronously;
                Result = result;
                Exception = e;
                IsCompleted = true;

                if (_callback != null)
                    _callback(this);
            }
        }
       
        protected abstract void DoWork();

        #endregion

        #region - Public Methods -
        
        public TResult WaitForResult()
        {
            if (!IsCompleted)
                AsyncWaitHandle.WaitOne();

            if (Exception != null)
            {
                if (Exception is TimeoutException && WebOperationContext.Current != null)
                    WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.RequestTimeout;

                throw Exception;
            }

            return Result;
        }

        #endregion

        #region - IAsyncResult Implementation -

        public object AsyncState { get; private set; }
        
        public WaitHandle AsyncWaitHandle { get; private set; }

        public bool CompletedSynchronously { get; private set; }
       
        public bool IsCompleted { get; private set; }

        #endregion
    }
در این حالت شما می‌توانید حداکثر زمان مورد نیاز برای درخواست را به عنوان پارامتر از طریق سازنده کلاس بالا تعیین نمایید. اگر این زمان بیش از زمان تعیین شده در خواص SendTimeout و ReceiveTimeout بود بعد از منقضی شدن زمان پردازش درخواست، به جای دریافت TimeoutException عملیات پردازش به کار خود ادامه خواهد داد.
برای استفاده از کلاس تهیه شده ابتدا باید عملیات خود را به صورت Async پیاده سازی نمایید که در این مقاله به صورت کامل شرح داده شده است.
یک مثال
قصد داریم Operation زیر را به صورت Long Polling پیاده سازی نماییم:
[OperationContract]
public string GetNotification();
ابتدا متد زیر باید به صورت Async تبدیل شود:
[OperationContract(AsyncPattern = true)]
public IAsyncResult BeginWaitNotification(AsyncCallback callback, object state);
 
public string EndWaitNotification(IAsyncResult result);
حال نوع بازگشتی سرویس مورد نظر را با استفاده از کلاس LongPollingAsyncResult به صورت زیر ایجاد خواهیم کرد:
public class MyNotificationResult : LongPollingAsyncResult<string>
{
   protected override DoWork()
    {
        کد‌های مورد نظر را اینجا قرار دهید
        base.Complete(...)
    }
}
در نهایت پیاده سازی متد‌های Begin و End همانند ذیل خواهد بود:
public IAsyncResult BeginWaitNotification(AsyncCallback callback, object state)
{
    return new MyNotificationResult(callback, state);
}
 
public string EndWaitNotification(IAsyncResult result)
{
    MyNotificationResult myResult = result as MyNotificationResult;
    if(myResult == null)
        throw new ArgumentException("result was of the wrong type!");
 
    myResult.WaitForResult();
    return myResult.Result;
}
در این حالت کلاینت می‌تواند یک درخواست به صورت LongPolling به سرور ارسال نماید و البته مدیریت این درخواست در یک thread دیگر انجام می‌گیرد که نتیجه آن از عدم تداخل پردازش این درخواست با سایر قسمت‌های برنامه است.

مطالب
مدیریت Instance در WCF
نحوه پیاده سازی و مدیریت Instance در پروژه‌های مبتنی بر WCF

نکته : آشنایی اولیه با مفاهیم WCF جهت درک صحیح مطالب الزامی است.

تشریح مسئله :  در صورتی که نیاز باشد که نمونه ساخته شده از سرویس (سمت سرور) به صورت Singleton  باشد بهترین روش برای پیاده سازی به چه صورت است.

برای شروع ابتدا مثال زیر را پیاده سازی می‌کنیم.
یک Contract به صورت زیر تعریف می‌کنیم:
[ServiceContract(SessionMode=SessionMode.Allowed)]
    public interface IMyService
    {
        [OperationContract]
        int GetData();             
    }

حالا یک سرویس برای پیاده سازی Interface بالا می‌نویسیم.
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall )]
    public class PerCallService : IMyService
    {
        int count;
        public int GetData()
        {
            return ++count;
        }
    }
همانطور که از نام سرویس مشخص است از این سرویس به ازای هر فراخوانی یک نمونه سمت سرور ساخته می‌شود.
حالا برای مشاهده نتیجه یک پروژه ConsoleApplication ایجاد کنید و سرویس مورد نظر را از روش AddServiceReference به پروژه اضافه کرده در فایل Program کد‌های زیر را کپی کنید.
 static void Main( string[] args )
        {
            Console.WriteLine( "PerCall Service" );

            MyPerCallService.MyServiceClient client = new MyPerCallService.MyServiceClient();
            int count = 0;
            for ( int i = 0 ; i < 5 ; i++ )
            {
                count = client.GetData();              
            }          
            Console.WriteLine( count );
            Console.ReadLine();         
        }
بعد از اجرا خروجی به صورت زیر است:

بعد از 5 بار فراخوانی متد GetData باز خروجی دارای مقدار 1 است. یعنی به ازای هر بار فراخوانی متد GetData یک نمونه از سرویس مورد نظر ساخته می‌شود.این عمل توسط خصوصیت InstanceContextMode که از نوع PerCall است به سرویس اعمال میشود.

حالا یک سرویس دیگر به صورت زیر ایجاد کنید.

 [ServiceBehavior( InstanceContextMode = InstanceContextMode.Single )]
    public class SingleService : IMyService
    {
        int count;
        public int GetData()
        {
            return ++count;
        }
    }
تنها تفاوت این سرویس با سرویس قبلی در این است که InstanceContextMode این سرویس  به صورت Single معرفی شده است. یعنی به ازای n فراخوانی فقط یک نمونه از کلاس ساخته می‌شود. این سرویس رو هم مثل روش قبلی به Client Application اضافه کنید.
کد کلاس Program رو به صورت زیر تغییر دهید.

static void Main( string[] args )
        {
            Console.WriteLine( "Single Service" );

            MySingleService.MyServiceClient client = new MySingleService.MyServiceClient();
            int count = 0;
            for ( int i = 0 ; i < 5 ; i++ )
            {
                count = client.GetData();              
            }          
            Console.WriteLine("Result is : {0}", count );
            Console.ReadLine();         
        }
که بعد از اجرا خروجی به صورت زیر است.

به ازای 5 بار فراخوانی سرویس متغیر Count سمت سرور مقدار قبلی خود را حفظ کرده است.

مطالب
تولید SiteMap استاندارد و ایجاد یک ActionResult اختصاصی برای Return کردن SiteMap تولید شده
یکی از item‌های مهم در بهینه سازی SEO یک وب‌سایت وجود یک SiteMap استاندارد متشکل از لینک‌های موجود در سایت هست که در وب‌سایت‌های داینامیک معمولا این لینک‌ها بر اساس داده‌های موجود در بانک اطلاعاتی ایجاد میشه. برای مثال مطالب، اخبار و ....
در اینجا بنده قبلا یک کلاس برای تولید SiteMap آماده کردم که در پروژه‌های خودم ازش استفاده میکنم. توسط این کلاس میتونید به صورت داینامیک SiteMap وب‌سایت مبتنی بر ASP.NET MVC خودتون رو ایجاد کنید.

برای آشنایی با ساختار یک SiteMap استاندارد میتونید به لینک رسمی روبرو مراجعه کنید : http://www.sitemaps.org/de/protocol.html 

بنده کلاس‌های زیر رو بر مبنای لینک مذکور در سایت رسمی SiteMaps تولید کردم.  بعد از تولید SiteMap نیاز دارید که اون رو مثلا به عنوان خروجی یک ActionResult بازگردونید. برای این کار هم یک کلاس با نام XmlResult  مشتق شده از ActionResult آماده سازی کردم که کلاس تولید شده SiteMap رو Serialize میکنه و به عنوان نتیجه‌ی یک Action باز می‌گردونه .
using System;
using System.Collections;
using System.Web.Mvc;
using System.Xml.Serialization;

namespace Neoox.Core.SeoTools
{
    [XmlRoot("urlset", Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9")]
    public class Sitemap
    {
        private ArrayList map;

        public Sitemap()
        {
            map = new ArrayList();
        }

        [XmlElement("url")]
        public Location[] Locations
        {
            get
            {
                Location[] items = new Location[map.Count];
                map.CopyTo(items);
                return items;
            }
            set
            {
                if (value == null)
                    return;
                Location[] items = (Location[])value;
                map.Clear();
                foreach (Location item in items)
                    map.Add(item);
            }
        }

        public int Add(Location item)
        {
            return map.Add(item);
        }
    }

    public class Location
    {
        public enum eChangeFrequency
        {
            always,
            hourly,
            daily,
            weekly,
            monthly,
            yearly,
            never
        }

        [XmlElement("loc")]
        public string Url { get; set; }

        [XmlElement("changefreq")]
        public eChangeFrequency? ChangeFrequency { get; set; }
        public bool ShouldSerializeChangeFrequency() { return ChangeFrequency.HasValue; }

        [XmlElement("lastmod")]
        public DateTime? LastModified { get; set; }
        public bool ShouldSerializeLastModified() { return LastModified.HasValue; }

        [XmlElement("priority")]
        public double? Priority { get; set; }
        public bool ShouldSerializePriority() { return Priority.HasValue; }
    }

    public class XmlResult : ActionResult
    {
        private object objectToSerialize;

        public XmlResult(object objectToSerialize)
        {
            this.objectToSerialize = objectToSerialize;
        }

        public object ObjectToSerialize
        {
            get { return this.objectToSerialize; }
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (this.objectToSerialize != null)
            {
                context.HttpContext.Response.Clear();
                var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType());
                context.HttpContext.Response.ContentType = "text/xml";
                xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
            }
        }
    }
}
 و اما نحوه استفاده از این کلاس‌ها هم خیلی سادست. به مثال زیر توجه کنید ... فقط این نکته رو در نظر داشته باشید که item هایی که به sitemap اضافه میشه واکشی شده از بانک اطلاعاتی هست، در این مثال چون خواستم ساده توضیح داده بشه نحوه استفاده از این کلاس‌ها، این داده‌ها به صورت static در نظر گرفته شد ولی شما میتونید داده‌ها رو بر اساس ساختار بانک اطلاعاتی خودتون واکشی کرده و به SiteMap اضافه کنید تا یک SiteMap کاملا پویا و Dynamic داشته باشید...
        public ActionResult Sitemap()
        {
            Sitemap sm = new Sitemap();
            sm.Add(new Location()
            {
                Url = string.Format("http://www.TechnoDesign.ir/Articles/{0}/{1}", 1, "SEO-in-ASP.NET-MVC"),
                LastModified = DateTime.UtcNow,
                Priority = 0.5D
            });
            return new XmlResult(sm);
        }
مطالب
پردازش توالی توالی‌ها در Reactive extensions
به صورت پیش فرض، Rx هر بار تنها یک مقدار را بررسی می‌کند. اما گاهی از اوقات نیاز است تا در هربار، بیشتر از یک مقدار دریافت و پردازش شوند. برای این منظور Rx متدهای الحاقی ویژه‌ای را به نام‌های Buffer ،Scan و Window تدارک دیده‌است تا بتواند از یک توالی، چندین توالی را تولید کند (توالی توالی‌ها = Sequence of sequences).


متد Scan

فرض کنید قصد دارید تعدادی عدد را با هم جمع بزنید. برای اینکار عموما عدد اول با عدد دوم جمع زده شده و سپس حاصل آن با عدد سوم جمع زده خواهد شد و به همین ترتیب تا آخر توالی. کار متد Scan نیز دقیقا به همین نحو است. هربار که قرار است توالی پردازش شود، حاصل عملیات مرحله‌ی قبل را در اختیار مصرف کننده قرار می‌دهد.
در مثال ذیل، قصد داریم حاصل جمع اعداد موجود در آرایه‌ای را بدست بیاوریم:
var sequence = new[] { 12, 3, -4, 7 }.ToObservable();
var runningSum = sequence.Scan((accumulator, value) =>
{
    Console.WriteLine("accumulator {0}", accumulator);
    Console.WriteLine("value {0}", value);
    return accumulator + value;
});
runningSum.Subscribe(result => Console.WriteLine("result {0}\n", result));
با این خروجی
result 12

accumulator 12
value 3
result 15

accumulator 15
value -4
result 11

accumulator 11
value 7
result 18
در اولین بار اجرای متد Subscribe، کار مقدار دهی accumulator با اولین عنصر آرایه صورت می‌گیرد.
در دفعات بعدی، مقدار این accumulator با عدد جاری جمع زده شده و حاصل این عملیات در تکرار آتی، مجددا توسط accumulator قابل دسترسی خواهد بود.

یک نکته: اگر علاقمند باشیم که مقدار اولیه‌ی accumulator، اولین عنصر توالی نباشد، می‌توان آن‌را توسط پارامتر seed متد Scan مقدار دهی کرد:
 var runningSum = sequence.Scan(seed: 10, accumulator: (accumulator, value) =>


متد Buffer

متد بافر، کار تقسیم یک توالی را به توالی‌های کوچکتر، بر اساس زمان، یا تعداد عنصر مشخص شده، انجام می‌دهد. برای مثال در برنامه‌های دسکتاپ شاید نیازی نباشد تا به ازای هر عنصر توالی، یکبار رابط کاربری را به روز کرد. عموما بهتر است تا تعداد مشخصی از عناصر یکجا پردازش شده و نتیجه‌ی این پردازش به تدریج نمایش داده شود.
var sequence = Enumerable.Range(1, 200)
                 .ToObservable()
                 .Buffer(count: 10);

sequence.Subscribe(onNext: numbers =>
{
   Console.WriteLine(numbers.Sum());
});
در اینجا نحوه‌ی استفاده از متد بافر را به همراه مشخص کردن تعداد اعضای بافر ملاحظه می‌کنید. هربار که onNext متد Subscribe فراخوانی شود، 10 عنصر از توالی را در اختیار خواهیم داشت (بجای یک عنصر حالت متداول بافر نشده).
به این ترتیب می‌توان فشار حجم اطلاعات ورودی با فرکانس بالا را کنترل کرد و در نتیجه از منابع موجود بهتر استفاده نمود. برای مثال اگر می‌خواهید عملیات bulk insert را انجام دهید، می‌توان بر اساس یک batch size مشخص، گروه گروه اطلاعات را به بانک اطلاعاتی اضافه کرد تا فشار کار کاهش یابد.

همینکار را بر اساس زمان نیز می‌توان انجام داد:
 var sequence = Enumerable.Range(1, 200)
                   .ToObservable()
                   .Buffer(timeSpan: TimeSpan.FromSeconds(2));
در مثال فوق هر 2 ثانیه یکبار، مجموعه‌ای از عناصر به متد onNext ارسال خواهند شد.


متد Window

متد Window نیز دقیقا همان پارامترهای متد بافر را قبول می‌کند. با این تفاوت که هربار، یک توالی obsevable را به متد onNext ارسال می‌کند.
نوع numbers پارامتر onNext، در حین بکارگیری متد بافر در مثال‌های فوق، IList of int است. اما اگر متدهای Buffer را تبدیل به متد Window کنیم، اینبار نوع numbers، معادل IObservable of int خواهد شد.
 var sequence = Enumerable.Range(1, 200)
                           .ToObservable()
                           .Window(timeSpan: TimeSpan.FromSeconds(2));

sequence.Subscribe(onNext: numbers =>
{
       numbers.Subscribe(onNext: number => Console.WriteLine(number));
});


چه زمانی باید از Buffer استفاده کرد و چه زمانی از Window؟

در متد بافر، به ازای هر توالی که به پارامتر onNext ارسال می‌شود، یکبار وهله‌ی جدیدی از توالی مدنظر در حافظه ایجاد و ارسال خواهد شد. در متد Window صرفا اشاره‌گرهایی به این توالی را در اختیار داریم؛ بنابراین مصرف حافظه‌ی کمتری را شاهد خواهیم بود. متد Window بسیار مناسب است برای اعمال aggregation. مثلا اگر نیاز است جمع، میانگین، حداقل و حداکثر عناصر دریافتی محاسبه شوند، بهتر است از متد Window استفاده شود که نهایتا قابلیت استفاده از متدهای الحاقی Sum و Max و Min را به همراه دارد. با این تفاوت که حاصل این‌ها نیز یک IObservable است که باید Subscribe آن‌را برای دریافت نتیجه فراخوانی کرد:
 var sequence = Enumerable.Range(1, 200)
                          .ToObservable()
                          .Window(10);

sequence.Subscribe(onNext: numbers =>
{
     numbers.Sum().Subscribe(onNext: number => Console.WriteLine(number));
});
در این حالت متد Window، برخلاف متد Buffer، توالی numbers را هربار کش نمی‌کند و به این ترتیب می‌توان به مصرف حافظه‌ی کمتری رسید.


کاربردهای دنیای واقعی

در اینجا دو مثال از بکارگیری متد Buffer را جهت پردازش مجموعه‌های عظیمی از اطلاعات و نمایش همزمان آن‌ها در رابط کاربری ملاحظه می‌کنید.
مثال اول: فرض کنید قصد دارید تمام فایل‌های درایو C خود را توسط یک TreeView نمایش دهید. در این حالت نباید رابط کاربری برنامه در حالت هنگ به نظر برسد. همچنین به علت زیاد بودن تعداد فایل‌ها و نمایش همزمان آن‌ها در UI، نباید CPU Usage برنامه تا حدی باشد که در کار سایر برنامه‌ها اخلال ایجاد کند. در این مثال‌ها با استفاده از Rx و متد بافر آن، هربار مثلا 1000 آیتم را بافر کرده و سپس یکجا در TreeView نمایش می‌دهند. به این ترتیب دو شرط یاد شده محقق می‌شوند.
The Rx Framework By Example

مثال دوم: خواندن تعداد زیادی رکورد از بانک اطلاعاتی به همراه نمایش همزمان آن‌ها در UI بدون اخلالی در کار سیستم و همچنین هنگ کردن برنامه.
Using Reactive Extensions for Streaming Data from Database
مطالب
فیلدهای پویا در NHibernate

یکی از قابلیت‌های جالب NHibernate امکان تعریف فیلدها به صورت پویا هستند. به این معنا که زیرساخت طراحی یک برنامه "فرم ساز" هم اکنون در اختیار شما است! سیستمی که امکان افزودن فیلدهای سفارشی را دارا است که توسط برنامه نویس در زمان طراحی اولیه آن ایجاد نشده‌اند. در ادامه نحوه‌ی تعریف و استفاده از این قابلیت را توسط Fluent NHibernate بررسی خواهیم کرد.

در اینجا کلاسی که قرار است توانایی افزودن فیلدهای سفارشی را داشته باشد به صورت زیر تعریف می‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فیلدهای سفارشی مورد نظر خواهند بود. جهت معرفی صحیح این قابلیت نیاز است تا نگاشت آن‌را از نوع dynamic component تعریف کنیم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترین نکته‌ی این بحث هم همین نگاشت فوق است.
ابتدا از IgnoreProperty جهت ندید گرفتن Attributes استفاده کردیم. زیرا درغیراینصورت در حالت Auto mapping ، یک رابطه چند به یک به علت وجود IDictionary به صورت خودکار ایجاد خواهد شد که نیازی به آن نیست (یافتن این نکته نصف روز کار برد ....! چون مرتبا خطای An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر می‌شد و مشخص نبود که مشکل از کجاست).
سپس Attributes به عنوان یک DynamicComponent معرفی شده است. در اینجا چهار فیلد سفارشی را اضافه کرده‌ایم. به این معنا که اگر نیاز باشد تا فیلد سفارشی دیگری به سیستم اضافه شود باید یکبار Session factory ساخته شود و SchemaUpdate فراخوانی گردد و خوشبختانه با وجود Fluent NHibernate ، تمام این تغییرات در کدهای برنامه قابل انجام است (بدون نیاز به سر و کار داشتن با فایل‌های XML نگاشت‌ها و ویرایش دستی آن‌ها). از تغییر نام جدول که برای مثال در اینجا tblDynamicEntity در نظر گرفته شده تا افزودن فیلدهای دیگر در قسمت DynamicComponent فوق.
همچنین باتوجه به اینکه این نوع تغییرات (ساخت دوبار سشن فکتوری) مواردی نیستند که قرار باشد هر ساعت انجام شوند، بنابراین سربار آنچنانی را به سیستم تحمیل نمی‌کنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

اگر با SchemaExport، اسکریپت خروجی معادل با نگاشت فوق را تهیه کنیم به جدول فوق خواهیم رسید. نوع و طول این فیلدهای سفارشی بر اساس نوعی که برای اشیاء دیکشنری مشخص می‌کنید، تعیین خواهند شد.

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

نحوه‌ی افزودن رکوردهای جدید بر اساس خاصیت‌های سفارشی:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجی
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ی کوئری گرفتن از این اطلاعات (فعلا پایدارترین روشی را که برای آن یافته‌ام استفاده از HQL می‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجی:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

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

نحوه‌ی به روز رسانی و حذف اطلاعات بر اساس فیلدهای پویا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}

مطالب
طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها

در این مطلب قصد داریم علاوه بر طراحی زیرساختی برای راه اندازی هرچه سریعتر ServiceLayer، طراحی ای برای مکانیزم Validation به عنوان یک Cross Cutting Concern، نیز ارائه داده و آن را پیاده سازی کنیم.

پیش نیازها:

 ServiceLayer در معماری لایه‌ای، در برگیرنده ApplicationService هایی می‌باشد که به عنوان مدخل ورودی (Entry Point) برنامه، در معرض دید لایه Presentation قرار گرفته و داده را به فرمت مورد نیاز Presentation در اختیارش قرار خواهند داد.
 این سرویس‌ها DTO‌ها را به عنوان پارامتر دریافت کرده و DTO هایی را به عنوان خروجی برگشت خواهند داد. مباحثی مانند Logging، Caching، Business Validation Authorization و مدیریت تراکنش‌ها را می‌توان در این لایه در نظر گرفت.

در ادامه اگر واژه «سرویس» به کار گرفته می‌شود منظور ما ApplicationServiceها می‌باشند.

کار را با ارائه یکسری واسط و کلاس پایه برای عملیات CRUD در سرویس‌ها به صورت زیر پیش می‌بریم.

قرار است به صورت قراردادی، تمام سرویس‌های ما واسط زیر را پیاده سازی کرده باشند. این مورد در مباحث تعریف Policy‌های مربوط به StructureMap مفید خواهد بود.

namespace MvcFramework.Framework.Application.Services
{
    public interface IApplicationService : ITransientDependency
    {
    }
}

دو واسط دیگر برای اعمال طول عمر اشیاء به صورت قراردادی در StructureMap به شکل زیر در نظر گرفته شده‌اند.

namespace MvcFramework.Framework.Dependency
{
    public interface ISingletonDependency
    {
    }
}
namespace MvcFramework.Framework.Dependency
{
    public interface ITransientDependency
    {
    }
}

و با پیاده سازی یک LifeCyclePolicy از دو واسط بالا به شکل زیر استفاده خواهیم کرد.

namespace MvcFramework.Framework.Dependency
{
    public class LifeCyclePolicy : IInstancePolicy
    {
        public void Apply(Type pluginType, Instance instance)
        {
            if (typeof(ISingletonDependency).IsAssignableFrom(instance.ReturnedType))
                instance.SetLifecycleTo<SingletonLifecycle>();
            else if (typeof(ITransientDependency).IsAssignableFrom(instance.ReturnedType))
                instance.SetLifecycleTo<TransientLifecycle>();
        }
    }
}

به این صورت تنظیم طول عمر اشیاء ساخته شده توسط StructureMap این بار به صورت قرادادی بوده و لازم به ذکر تک تک این موارد در تنظیمات اولیه مربوط به Container آن نیست.

کلاس پایه‌ای را که پیاده ساز واسط IApplicationService می‌باشد، برای مقابله با عدم نگارش پذیری واسط‌ها، به شکل زیر در نظر میگیریم. 

namespace MvcFramework.Framework.Application.Services
{
    public abstract class ApplicationService : IApplicationService
    {
    }
}

بسته به نیاز پروژه خودتان می‌توانید اعضای مشترک بین سرویس‌ها را در دل این کلاس قرار دهید.

در ادامه واسط ICrudApplicationSevie را به شکل زیر طراحی خواهیم کرد.

namespace MvcFramework.Framework.Application.Services
{
    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, DynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TDynamicListRequest> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, TDynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TDynamicListRequest : DynamicListRequest
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TPagedListRequest,
        TPagedListResponse> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest, TPagedListResponse,
            DynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TPagedListRequest,
        TPagedListResponse,
        in TDynamicListRequest> : IApplicationService
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>
        where TDynamicListRequest : DynamicListRequest
    {
        void Create(TCreateModel model);
        void Create(IList<TCreateModel> models);
        Task CreateAsync(TCreateModel model);
        Task CreateAsync(IList<TCreateModel> models);

        IList<TModel> GetList();
        DynamicListResponse GetDynamicList(TDynamicListRequest request);
        TPagedListResponse GetPagedList(TPagedListRequest request);
        IList<LookupItem> GetLookup();
        TModel GetById(long id);
        TEditModel GetForEdit(long id);
        bool Exists(long id);
        Task<IList<TModel>> GetListAsync();
        Task<DynamicListResponse> GetDynamicListAsync(TDynamicListRequest request);
        Task<TPagedListResponse> GetPagedListAsync(TPagedListRequest request);
        Task<IList<LookupItem>> GetLookupAsync();
        Task<TModel> GetByIdAsync(long id);
        Task<TEditModel> GetForEditAsync(long id);
        Task<bool> ExistsAsync(long id);

        void Edit(TEditModel model);
        void Edit(IList<TEditModel> models);
        Task EditAsync(TEditModel model);
        Task EditAsync(IList<TEditModel> models);
        
        void Delete(TDeleteModel model);
        void Delete(IList<TDeleteModel> models);
        Task DeleteAsync(TDeleteModel model);
        Task DeleteAsync(IList<TDeleteModel> models);
    }
}

سرویسی که نیاز دارد از عملیات CRUD نیز پشتیبانی داشته باشد، بهتر است واسط آن از یک چنین واسطی که در بالا معرفی شد، ارث بری کند. 

مدل‌ها و واسط‌های پیش فرضی را که در واسط بالا از آنها استفاده شده است، در زیر مشاهده می‌کنید:

 واسط IModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IModel
    {
        long Id { get; set; }
    }
}

واسط IEditModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IEditModel : IModel
    {
        byte[] RowVersion { get; set; }
    }
}

واسط IDeleteModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IDeleteModel : IModel
    {
        byte[] RowVersion { get; set; }
    }
}

کلاس LookupItem

namespace MvcFramework.Framework.Application.Models
{
    public class LookupItem
    {
        public string Value { get; set; }
        public string Text { get; set; }
        public bool Selected { get; set; }
    }
}

کلاس PagedListRequest

namespace MvcFramework.Framework.Application.Models
{
    public class PagedListRequest : IShouldNormalize
    {
        public long TotalCount { get; set; }
        public int PageNumber { get; set; }
        public int PageSize { get; set; }

        /// <summary>
        ///     Sorting information.
        ///     Should include sorting field and optionally a direction (ASC or DESC)
        ///     Can contain more than one field separated by comma (,).
        /// </summary>
        /// <example>
        ///     Examples:
        ///     "Name"
        ///     "Name DESC"
        ///     "Name ASC, Age DESC"
        /// </example>
        public string SortBy { get; set; }

        public void Normalize()
        {
            if (PageNumber < 1)
                PageNumber = 1;

            if (PageSize < 0)
                PageSize = 10;

            if (SortBy.IsEmpty())
                SortBy = "Id DESC";
        }
    }
}

در این طراحی دو شکل از GetPagedList در نظر گرفته شده است؛ یکی با ورودی و خروجی داینامیک مثلا جهت استفاده برای نمایش اطلاعات در کندو گرید که در ادامه با آن بیشتر آشنا خواهید شد و دیگری هم برای زمانیکه نیاز دارید اطلاعات صفحه بندی شده‌ای را در اختیار داشته باشید. کلاس بالا برای پیاده سازی شکل دومی که صحبت شد، استفاده میشود. پیاده سازی واسط IShouldNormalize باعث خواهد شد که قبل از اجرای خود متد، این نوع پارامترها با استفاده از یک Interceptor شناسایی شده و متد Normalize آنها اجرا شود.


کلاس PagedListResponse

namespace MvcFramework.Framework.Application.Models
{
    public class PagedListResponse<TModel, TPagedListRequest>
        where TPagedListRequest : PagedListRequest, new()
        where TModel : IModel
    {
        public PagedListResponse()
        {
            Result = new List<TModel>();
            Request = new TPagedListRequest();
        }
        public IList<TModel> Result { get; set; }
        public TPagedListRequest Request { get; set; }
    }
}

کلاس بالا به عنوان نوع خروجی متد GetPagedList مورد استفاده قرار میگرد. وجود خصوصیتی از نوع PagedListRequest هم برای مواردی مانند صفحه بندی نیز می‌تواند مفید باشد.


کلاس‌های DynamicListRequest و DynamicListResponse برگرفته از کتابخانه Kendo.DynamicLinq می باشند.


کلاس Entity

namespace MvcFramework.Framework.Domain.Entities
{
    public abstract class Entity
    {
        #region Properties

        public long Id { get; set; }
        public byte[] RowVersion { get; set; }
        public EntityChangeState State { get; set; }

        #endregion

        #region Public Methods

        [SuppressMessage("ReSharper", "BaseObjectGetHashCodeCallInGetHashCode")]
        [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
        public override int GetHashCode()
        {
            if (IsTransient())
                return base.GetHashCode();

            unchecked
            {
                var hash = this.GetRealType().GetHashCode();
                return (hash * 31) ^ Id.GetHashCode();
            }
        }

        public virtual bool IsTransient()
        {
            return Id == 0;
        }

        public override bool Equals(object obj)
        {
            var other = obj as Entity;
            if (ReferenceEquals(other, null)) return false;

            if (ReferenceEquals(this, other)) return true;

            var typeOfThis = this.GetRealType();
            var typeOfOther = other.GetRealType();

            if (typeOfThis != typeOfOther) return false;

            if (IsTransient() || other.IsTransient()) return false;

            return Id.Equals(other.Id);
        }

        public override string ToString()
        {
            return $"[{this.GetRealType().Name} : {Id}]";
        }

        #endregion

        #region Operators

        public static bool operator ==(Entity left, Entity right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Entity left, Entity right)
        {
            return !(left == right);
        }

        #endregion
    }
}

در این کلاس یکسری خصوصیات پایه ای مانند Id و متدهای مشترک بین Entityها قرار گرفته شده است. این کلاس پایه تمام Entity‌های سیستم می‌باشد.

پیاده سازی پیش فرض از واسط ICrudApplicationService به شکل زیر می‌باشد.

namespace MvcFramework.Framework.Application.Services
{
    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, DynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TDynamicListRequest> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, TDynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TDynamicListRequest : DynamicListRequest
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TPagedListRequest,
        TPagedListResponse> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest,
            TPagedListResponse,
            DynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>, new()
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TPagedListRequest,
        TPagedListResponse, TDynamicListRequest> : ApplicationService,
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest, TPagedListResponse,
            TDynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>, new()
        where TDynamicListRequest : DynamicListRequest

    {
        #region Constructor

        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper)
        {
            Guard.ArgumentNotNull(unitOfWork, nameof(unitOfWork));
            Guard.ArgumentNotNull(mapper, nameof(mapper));

            UnitOfWork = unitOfWork;
            Mapper = mapper;
            EntitySet = UnitOfWork.Set<TEntity>();
        }

        #endregion

        #region Properties

        protected IQueryable<TEntity> UnTrackedEntitySet => EntitySet.AsNoTracking();
        protected IUnitOfWork UnitOfWork { get; }
        protected IMapper Mapper { get; }
        protected IDbSet<TEntity> EntitySet { get; }

        #endregion

        #region ICrudApplicationService Members

        #region Methods

        [Transactional]
        public virtual void Create(TCreateModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            EntitySet.Add(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Create(IList<TCreateModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.AddRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task CreateAsync(TCreateModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            EntitySet.Add(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task CreateAsync(IList<TCreateModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.AddRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }


        [Transactional]
        public virtual void Edit(TEditModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsChanged(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Edit(IList<TEditModel> models)
        {
            Guard.ArgumentNotNull(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.UpdateRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task EditAsync(TEditModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsChanged(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task EditAsync(IList<TEditModel> models)
        {
            Guard.ArgumentNotNull(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.UpdateRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }


        public virtual IList<TModel> GetList()
        {
            return EntitySet.ProjectToList<TModel>(Mapper.ConfigurationProvider);
        }

        public virtual DynamicListResponse GetDynamicList(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            return query.ProjectTo<TModel>().ToListResponse(request);
        }

        public virtual TPagedListResponse GetPagedList(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            request.TotalCount = query.LongCount();

            query = ApplySorting(query, request);
            query = ApplyPaging(query, request);

            var result = query.ProjectToList<TModel>(Mapper.ConfigurationProvider);

            return new TPagedListResponse
            {
                Result = result,
                Request = request
            };
        }

        public virtual IList<LookupItem> GetLookup()
        {
            return EntitySet.ProjectToList<LookupItem>(Mapper.ConfigurationProvider);
        }

        public virtual TModel GetById(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity =
                EntitySet.Where(a => a.Id == id).ProjectToFirstOrDefault<TModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetById");

            return entity;
        }

        public virtual TEditModel GetForEdit(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity =
                EntitySet.Where(a => a.Id == id).ProjectToFirstOrDefault<TEditModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetForEdit");

            return entity;
        }

        public virtual bool Exists(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            return EntitySet.Any(a => a.Id == id);
        }

        public virtual async Task<IList<TModel>> GetListAsync()
        {
            return await EntitySet.ProjectToListAsync<TModel>(Mapper.ConfigurationProvider);
        }

        public virtual Task<DynamicListResponse> GetDynamicListAsync(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            return query.ProjectTo<TModel>().ToListResponseAsync(request);
        }

        public virtual async Task<TPagedListResponse> GetPagedListAsync(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            request.TotalCount = await query.LongCountAsync().ConfigureAwait(false);

            query = ApplySorting(query, request);
            query = ApplyPaging(query, request);

            var result = await query.ProjectToListAsync<TModel>(Mapper.ConfigurationProvider).ConfigureAwait(false);

            return new TPagedListResponse
            {
                Result = result,
                Request = request
            };
        }

        public virtual async Task<IList<LookupItem>> GetLookupAsync()
        {
            return await EntitySet.ProjectToListAsync<LookupItem>(Mapper.ConfigurationProvider);
        }

        public virtual async Task<TModel> GetByIdAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
                .ProjectToFirstOrDefaultAsync<TModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetByIdAsync");

            return entity;
        }

        public virtual async Task<TEditModel> GetForEditAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
                .ProjectToFirstOrDefaultAsync<TEditModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetForEditAsync");

            return entity;
        }

        public virtual Task<bool> ExistsAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            return EntitySet.AnyAsync(a => a.Id == id);
        }


        [Transactional]
        public virtual void Delete(TDeleteModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsDeleted(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Delete(IList<TDeleteModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.RemoveRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task DeleteAsync(TDeleteModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsDeleted(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task DeleteAsync(IList<TDeleteModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.RemoveRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }

        #endregion

        #endregion

        #region Protected Methods

        /// <summary>
        ///     Apply Filtering To GetDynamicList
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyFiltering(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            return UnTrackedEntitySet;
        }

        /// <summary>
        ///     Apply Filtering To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyFiltering(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            return UnTrackedEntitySet;
        }

        /// <summary>
        ///     Apply Sorting To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="query">query</param>
        /// <param name="request">PagedListRequest</param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return !request.SortBy.IsEmpty() ? query.OrderBy(request.SortBy) : query.OrderByDescending(e => e.Id);
        }

        /// <summary>
        ///     Apply Paging To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="request">PagedListRequest</param>
        /// <param name="query">query</param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return request != null
                ? query.Page((request.PageNumber - 1) * request.PageSize, request.PageSize)
                : query;
        }

        #endregion
    }
}

همه متد‌های این کلاس پایه، قابلیت override شدن را دارند. به عنوان مثال یکسری متد با دسترسی protected مثلا ApplyFiltering هم برای بازنویسی نحوه فیلتر کردن خروجی GetPagedList می‌توانند در SubClassها مورد استفاده قرار گیرند. برای مباحث مرتب سازی هم از کتابخانه System.Linq.Dynamic استفاده شده است. 

برای مکانیزم Validation خودکار هم از کتابخانه FluentValidatoin کمک گرفته شده است و با استفاده از Interceptor زیر در صورت یافتن Validator مربوط به Model ورودی، عملیات اعتبارسنجی انجام میگرد و در صورت معتبر نبودن، استثنایی صادر خواهد شد که حاوی اطلاعات مربوط به جزئیات خطاها نیز می‌باشد.

ValidatorInterceptor

namespace MvcFramework.Framework.Aspects.Validation
{
    public class ValidatorInterceptor : ISyncInterceptionBehavior
    {
        private readonly IValidatorFactory _validatorFactory;

        public ValidatorInterceptor(IValidatorFactory validatorFactory)
        {
            _validatorFactory = validatorFactory;
        }

        public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation)
        {
            var argumentValues = methodInvocation.Arguments.Select(a => a.Value).ToArray();

            var validator = new MethodInvocationValidator(_validatorFactory, methodInvocation.MethodInfo,
                argumentValues);

            validator.Validate();

            return methodInvocation.InvokeNext();
        }
    }
}

کتابخانه جانبی دیگری برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شده است. نمونه‌ی استفاده از آن، در بالا مشخص می‌باشد. در اینجا انتقال مسئولیت اعتبارسنجی پارامترهای متدی که قرار است Intercept شود، به کلاسی به نام MethodInvocationValidator سپرده شده‌است.

کلاس MethodInvocationValidator

namespace MvcFramework.Framework.Aspects.Validation
{
    internal class MethodInvocationValidator
    {
        #region Constructor

        public MethodInvocationValidator(IValidatorFactory validatorFactory, MethodInfo method,
            object[] parameterValues)
        {
            Guard.ArgumentNotNull(method, nameof(method));
            Guard.ArgumentNotNull(parameterValues, nameof(parameterValues));
            Guard.ArgumentNotNull(validatorFactory, nameof(validatorFactory));

            _method = method;
            _parameterValues = parameterValues;
            _validatorFactory = validatorFactory;
            _parameters = method.GetParameters();

            _parametersToBeNormalized = new List<IShouldNormalize>();
        }

        #endregion

        #region Public Methods

        public void Validate()
        {
            if (!CheckShouldBeValidate()) return;

            foreach (var parameterValue in _parameterValues)
                ValidateMethodParameter(parameterValue);

            foreach (var parameterToBeNormalized in _parametersToBeNormalized)
                parameterToBeNormalized.Normalize();
        }

        #endregion

        #region Fields

        private readonly MethodInfo _method;
        private readonly object[] _parameterValues;
        private readonly ParameterInfo[] _parameters;
        private readonly IValidatorFactory _validatorFactory;
        private readonly List<IShouldNormalize> _parametersToBeNormalized;

        #endregion

        #region Private Methods

        private bool CheckShouldBeValidate()
        {
            if (!_method.IsPublic)
                return false;

            if (IsValidationDisabled())
                return false;

            if (_parameters.IsNullOrEmpty())
                return false;

            if (_parameters.Length != _parameterValues.Length)
                throw new Exception("Method parameter count does not match with argument count!");

            return true;
        }

        private bool IsValidationDisabled()
        {
            if (_method.IsDefined(typeof(EnableValidationAttribute), true))
                return false;

            return ReflectionHelper
                       .GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(_method) != null;
        }

        private void ValidateMethodParameter(object parameterValue)
        {
            if (parameterValue == null) return;

            var parameterValueList = parameterValue as IEnumerable<object>;
            if (parameterValueList != null)
            {
                var valueList = parameterValueList.ToList();

                ValidateMethodParameterValues(valueList);
            }
            else
            {
                ValidateMethodParameterValues(new List<object> { parameterValue });
            }

            if (parameterValue is IShouldNormalize)
                _parametersToBeNormalized.Add(parameterValue as IShouldNormalize);
        }

        private void ValidateMethodParameterValues(List<object> valueList)
        {
            var ruleSet = GetRuleSet(_method);

            var validator = _validatorFactory.GetValidator(valueList.First().GetType());
            if (validator == null) return;

            foreach (var item in valueList)
                ValidateWithReflection(validator, item, ruleSet);
        }

        private static string GetRuleSet(MemberInfo method)
        {
            const string @default = "default";

            var attribute = method.GetCustomAttribute<ValidateWithRuleAttribute>();

            if (attribute == null)
                return @default;

            var rules = new List<string> { @default };

            rules.AddRange(attribute.RuleSetNames);

            return string.Join(",", rules).TrimEnd(',');
        }

        private static void ValidateAndThrow<T>(IValidator<T> validator, T argument, string ruleSet)
        {
            validator.ValidateAndThrow(argument, ruleSet);
        }

        private void ValidateWithReflection(IValidator validator, object argument, string ruleSet)
        {
            GetType().GetMethod(nameof(ValidateAndThrow), BindingFlags.Static | BindingFlags.NonPublic)
                .MakeGenericMethod(argument.GetType())
                .Invoke(null, new[] { validator, argument, ruleSet });
        }

        #endregion
    }
}

در متد Validate آن ابتدا چک می‌شود که آیا اعتبارسنجی می‌بایستی انجام شود یا خیر. سپس تک تک آرگومان‌های ارسالی را با استفاده از متد ValidateMethodParameter وارد مکانیزم اعتبارسنجی می‌کند. در داخل این متد ابتدا نوع آرگومان تشخیص داده شده و این مقادیر به متد ValidateMethodParameterValues ارسال شده و داخل آن ابتدا Validator مرتبط را یافته و آن را به متد ValidateWithReflection ارسال می‌کند. در این بین متد GetRuleSets وظیفه واکشی اسامی RuleSet هایی که بر روی متد مورد نظر تنظیم شده اند را دارد؛ برای مواقعی که از یک ویومدل برای ویرایش، درج و حذف استفاده کنید، در این صورت با توجه به اینکه برای یک ویومدل یک Validator خواهید داشت، امکانات RuleSet مربوط به FluentValidation کارساز خواهند بود. به این صورت که برای هر کدام از عملیات حذف، ویرایش و درج، RuleSet مناسب را تعریف کرده و با استفاده از ValidateWithRuleAttribute برروی متدهای مورد نظر، این ruleها در سیستم اعتبارسنجی ارائه شده اعمال خواهند شد.

با توجه به اینکه متد ValidateAndThrow در واسط IValidator‎<T>‎ تعریف شده‌است و از آنجاییکه ما نوع داده مدل مورد نظر را هم نداریم لازم است با استفاده از MakeGenericMethod به صورت داینامیک نوع داده T را مشخص کنیم و فراخوانی متد استاتیک ValidatorWithThrow‎<T>‎ را با Reflection انجام دهیم.

در ادامه لازم است ValidatorInterceptor معرفی شده را به StructureMap نیز معرفی کنیم. برای این منظور به شکل زیر عمل خواهیم کرد.

namespace MvcFramework.Framework
{
    public class FrameworkRegistry : Registry
    {
        public FrameworkRegistry()
        {
            For<IValidatorFactory>().Singleton().Use<StructureMapValidatorFactory>();

            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.LookForRegistries();
            });

            Policies.Interceptors(new DynamicProxyInterceptorPolicy(f => typeof(IApplicationService).IsAssignableFrom(f), typeof(ValidatorInterceptor),typeof(TransactionInterceptor)));
        }
    }
}

در کد بالا با استفاده از DynamicProxyInterceptorPolicy، یک Policy را برای Intercept کردن متدهای مربوط به کلاس هایی که پیاده ساز IApplicationService می‌باشند، معرفی کرده‌ایم.

کار اعتبارسنجی هم به پایان رسید؛ در زیر استفاده از سرویس پایه معرفی شده را می‌توانید مشاهده کنید.

namespace MyApp.ServiceLayer.Roles
{
    public interface IRoleApplicationService :
        ICrudApplicationService<RoleViewModel, RoleCreateViewModel, RoleEditViewModel, RoleDeleteViewModel, RolePagedListRequest, RoleListViewModel>
    {
    }
}

namespace MyApp.ServiceLayer.Roles
{
    public class RoleApplicationService :
        CrudApplicationService<Role, RoleViewModel, RoleCreateViewModel, RoleEditViewModel, RoleDeleteViewModel, RolePagedListRequest, RoleListViewModel>,
        IRoleApplicationService
    {
        #region Constructor

        public RoleApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }

        #endregion
    }
}


نکته: در این لایه بندی نکات مربوط به مطلب «پیاده سازی ماژولار Autofac» نیز با استفاده از StructureMap اعمال شده است. بدین ترتیب در هر لایه یک Registry مربوط به StructureMap ایجاد شده است. به شکل زیر:

FrameworkRegistry

namespace MyApp.Framework
{
    public class FrameworkRegistry : Registry
    {
        public FrameworkRegistry()
        {
            For<IValidatorFactory>().Singleton().Use<StructureMapValidatorFactory>();

            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.LookForRegistries();
            });

            Policies.Interceptors(new DynamicProxyInterceptorPolicy(f => typeof(IApplicationService).IsAssignableFrom(f), typeof(ValidatorInterceptor)/*, typeof(TransactionInterceptor)*/));
        }
    }
}


DataLayerRegistry

namespace MyApp.DataLayer
{
    public class DataLayerRegistry : Registry
    {
        public DataLayerRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnStartTask>();
            });

            //todo:use container per request (Nested Containers) instead of HttpContextLifeCycle
            For<IUnitOfWork>().Use<ApplicationDbContext>();
        }
    }
}


ServiceLayerRegistry

namespace MyApp.ServiceLayer
{
    public class ServiceLayerRegistry : Registry
    {
        #region Constructor

        public ServiceLayerRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.Assembly(typeof(DataLayerRegistry).Assembly);
                scan.LookForRegistries();

                scan.AddAllTypesOf<Profile>().NameBy(item => item.FullName);
                scan.AddAllTypesOf<IHaveCustomMappings>().NameBy(item => item.FullName);
            });

            FluentValidationConfig();
            AutoMapperConfig();
        }

        #endregion

        #region Private Methods

        private void AutoMapperConfig()
        {
            For<MapperConfiguration>().Singleton().Use("MapperConfig", ctx =>
            {
                var config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMissingTypeMaps = true;
                    AddProfiles(ctx, cfg);
                    AddIHaveCustomMappings(ctx, cfg);
                    AddMapFrom(cfg);
                });

                config.AssertConfigurationIsValid();

                return config;
            });

            For<IMapper>().Singleton().Use(ctx => ctx.GetInstance<MapperConfiguration>().CreateMapper(ctx.GetInstance));
        }

        private void FluentValidationConfig()
        {
            AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
                .ForEach(result =>
                {
                    For(result.InterfaceType)
                        .Singleton()
                        .Use(result.ValidatorType);
                });
        }

        private static void AddMapFrom(IProfileExpression cfg)
        {
            var types = typeof(RoleViewModel).Assembly.GetExportedTypes();
            var maps = (from t in types
                        from i in t.GetInterfaces()
                        where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>) && !t.IsAbstract &&
                              !t.IsInterface
                        select new
                        {
                            Source = i.GetGenericArguments()[0],
                            Destination = t
                        }).ToArray();

            foreach (var map in maps)
                cfg.CreateMap(map.Source, map.Destination);
        }

        private static void AddProfiles(IContext ctx, IMapperConfigurationExpression cfg)
        {
            var profiles = ctx.GetAllInstances<Profile>().ToList();
            foreach (var profile in profiles)
                cfg.AddProfile(profile);
        }

        private static void AddIHaveCustomMappings(IContext ctx, IMapperConfigurationExpression cfg)
        {
            var mappings = ctx.GetAllInstances<IHaveCustomMappings>().ToList();
            foreach (var mapping in mappings)
                mapping.CreateMappings(cfg);
        }

        #endregion
    }
}


WebRegistry

namespace MyApp.Web
{
    public class WebRegistry : Registry
    {
        public WebRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.Assembly(typeof(ServiceLayerRegistry).Assembly);
                scan.LookForRegistries();
            });
        }
    }
}

در این طراحی، لایه Web یا همان Presentation به DataLayer و DomainClasses هیچ ارجاعی ندارد.


در قسمت بعد استفاده از این سرویس را در یک برنامه ASP.NET MVC با هم بررسی خواهیم کرد. 

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید.

مطالب
صفحه بندی اطلاعات در ASP.NET MVC به روش HashChange
یکی از مواردی که درپروژه‌‌ها زیاد مورد استفاده قرار میگیرد، نمایش داده‌های ذخیره شده‌ی در بانک اطلاعاتی، به صورت صفحه بندی شده به کاربر می‌باشد. قبلا در زمینه بحث Paging، مطلبی تهیه شده بود و در این مقاله قصد داریم کتابخانه‌ای را مورد بررسی قرار دهیم که علاوه بر ارسال داده به صورت Ajax ایی، بتواند همچنین پارامترهای مورد نظر را به صورت Query String نیز در آدرس بار نمایش دهد.
اگر به جستجوی گوگل دقت کرده باشید، به صورت Ajax ایی پیاده سازی شده‌است، با این تفاوت که بعد از هر تغییر درجستجوی مورد نظر، Url صفحه نیز تغییر میکند (برای مثال بعد از جستجوی عبارت dotNetTips  آدرس بار صفحه به شکل https://www.google.com/#q=dotNetTips&* تغییر می‌کند). برای پیاده سازی این ویژگی باید از تکنیکی به نام HashChange استفاده کرد. در نتیجه با این روش مشکل ارسال صفحه‌ای خاص در یک گرید برای دیگران، به صورت Ajax ایی و بدون مشکل انجام می‌شود. از این رو با توجه به داشتن Url‌های منحصر به فرد برای هر صفحه، تا حدی مشکل سئو سایت را نیز برطرف می‌کنیم.

برای استفاده از این ویژگی در ادامه قصد داریم پیاده سازی کتابخانه‌ی MvcAjaxPager را مورد بررسی قرار دهیم. ابتدا قبل از هر کاری، با استفاده از دستور زیر اقدام به نصب کتابخانه آن می‌نماییم:
 Install-Package MvcAjaxPager

در ادامه نحوه پیاده سازی آن را به همراه مثالی، مورد بررسی قرار می‌دهیم:

ابتدا یک مدل فرضی را همانند زیر تهیه می‌کنیم :
public class Topic
{
   public int Id;
   public string Title;
   public string Text;
}
و کلاسی را همانند زیر برای دریافت یک لیست از مطالب می‌نویسیم:
public class TopicService
{
    public static IEnumerable<Topic> Topics = new List<Topic>() {
       new Topic{Id=1,Title="Title 1",Text= "Text 1"},
       new Topic{Id=2,Title="Title 2",Text="Text 2"},
       new Topic{Id=3,Title="Title 3",Text="Text 3"},
       new Topic{Id=4,Title="Title 4",Text="Text 4"},
       new Topic{Id=5,Title="Title 5",Text="Text 5"},
       new Topic{Id=6,Title="Title 6",Text="Text 6"},
       new Topic{Id=7,Title="Title 7",Text="Text 7"},
       new Topic{Id=8,Title="Title 8",Text="Text 8"},
       new Topic{Id=9,Title="Title 9",Text="Text 9"},
       new Topic{Id=10,Title="Title 10",Text="Text 10"},
       new Topic{Id=11,Title="Title 11",Text="Text 11"},
       new Topic{Id=12,Title="Title 12",Text="Text 12"},
       new Topic{Id=13,Title="Title 13",Text="Text 13"},
       new Topic{Id=14,Title="Title 14",Text="Text 14"},
       new Topic{Id=15,Title="Title 15",Text="Text 15"},
       new Topic{Id=16,Title="Title 16",Text="Text 16"},
       new Topic{Id=17,Title="Title 17",Text="Text 17"},
       new Topic{Id=18,Title="Title 18",Text="Text 18"},
       new Topic{Id=19,Title="Title 19",Text="Text 19"},
       new Topic{Id=20,Title="Title 20",Text="Text 20"},
       new Topic{Id=21,Title="Title 21",Text="Text 21"},
       new Topic{Id=22,Title="Title 22",Text="Text 22"},
      };

    public static IEnumerable<Topic> GetAll()
    {
       return Topics.OrderBy(row => row.Id);
    }
}
همچنین کلاس زیر را اضافه میکنیم:
public class ListViewModel
{
   public IEnumerable<Topic> Topics { get; set; }
   public int PageIndex { get; set; }
   public int TotalItemCount { get; set; }
}
ابتدا یک کنترلر را ایجاد می‌کنیم به همراه اکشن متدی که قصد داریم لیستی از اطلاعات را به کاربر نمایش دهیم:
public ActionResult Index(int page = 1)
{
       var topics = TopicService.GetAll ();
       int totalItemCount = topics.Count();
       var model = new ListViewModel()
       {
              PageIndex = page,
              Topics = topics.OrderBy(p => p.Id).Skip((page - 1) * 10).Take(10).ToList(),
              TotalItemCount = totalItemCount
       };

       if (!Request.IsAjaxRequest())
       {
              return View(model);
       }

       return PartialView("_TopicList", model);
}
در اینجا بعد از واکشی اطلاعات، تعداد 10 رکورد را در هر صفحه نمایش می‌دهیم. 

و در Partial view مربوطه نیز داریم :
@using MvcAjaxPager
@model ListViewModel

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = false,
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

<table>
    <tr>
        <th>
            @Html.DisplayName("ID")
        </th>
        <th>
            @Html.DisplayName("Title")
        </th>
        <th>
            @Html.DisplayName("Text")
        </th>
    </tr>

    @foreach (var topic in Model.Topics)
    {
        <tr>
            <td>
                @topic.Id
        </td>
        <td>
            @topic.Title
        </td>
        <td>
            @topic.Text
        </td>
    </tr>
    }
</table>

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = true,
       FirstPageText = "اولین",
       LastPageText = "آخرین",
       MorePageText = "...",
       PrevPageText = "قبلی",
       NextPageText = "بعدی",
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

 حال برای استفاده از pager مورد نظر فقط کافیست متد AjaxPager آن را فراخوانی کنیم. این متد شامل 11  OverLoad مختلف هست.
در این قسمت TotalItemCount جمع کل رکورد‌ها، PageSize تعداد رکورد‌های هر صفحه و PageIndex آدرس صفحه جاری می‌باشد.

مهمترین بخش این pager  که قابلیت‌های زیادی را به کاربر می‌دهد، قسمت PagerOptions آن است و تعدادی از پارامتر‌های آن شامل AjaxOnBeginAjaxOnCompelte، AjaxOnSuccess ،  AjaxOnFailure میتوان تعیین کرد تا بعد از شروع، وقوع خطا، موفقیت و یا خاتمه عملیات جاوا اسکریپتی، اجرا شود. 

AlwaysShowFirstLastPageNumber جهت نمایش صفحه اول و آخر
FirstPageText جهت تعیین متن اولین صفحه
LastPageText جهت تعیین متن آخرین صفحه
CssClass ، Id  جهت تعیین Id خاص

و در انتها، در view مربوطه داریم:
@using MvcAjaxPager
@model ListViewModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div id="dvTopics">
        @{
            @Html.Partial("_TopicList", Model);
        }
    </div>

    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/path.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.pager-1.0.1.min.js")"></script>
    <script type="text/javascript">
        $('.NavigationBox').pager();

        //pagination before start
        function AjaxStart() {
            console.log('Start AJAX call. Loading message can be shown');
        }
        // pagination - after request
        function AjaxStop() {
            console.log('Stop AJAX call. Loading message can be hidden');
        };
    </script>
</body>
</html>
در انتهای صفحه مورد نظر می‌بایست دو فایل جاوااسکریپتی jquerypager و Path را که هنگام نصب Pager، به برنامه اضافه شده اند، فراخوانی کنیم و با استفاده از CssClass  یا Id که قبلا در بخش PagerOption تعیین کردیم، آن را انتخاب و متدpager را فراخوانی کنیم.
مطالب دوره‌ها
تزریق خودکار وابستگی‌ها در ASP.NET Web API به همراه رها سازی خودکار منابع IDisposable
در انتهای مطلب « تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC » اشاره‌ای کوتاه به روش DependencyResolver توکار Web API شد که این روش پس از بررسی‌های بیشتر (^ و ^) به دلیل ماهیت service locator بودن آن و همچنین از دست دادن Context جاری Web API، مردود اعلام شده و استفاده از IHttpControllerActivator توصیه می‌گردد. در ادامه این روش را توسط Structure map 3 پیاده سازی خواهیم کرد.

پیش نیازها
- شروع یک پروژه‌ی جدید وب با پشتیبانی از Web API
- نصب دو بسته‌ی نیوگت مرتبط با Structure map 3
 PM>install-package structuremap
PM>install-package structuremap.web

پیاده سازی IHttpControllerActivator توسط Structure map

using System;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using StructureMap;

namespace WebApiDISample.Core
{
    public class StructureMapHttpControllerActivator : IHttpControllerActivator
    {
        private readonly IContainer _container;
        public StructureMapHttpControllerActivator(IContainer container)
        {
            _container = container;
        }

        public IHttpController Create(
                HttpRequestMessage request,
                HttpControllerDescriptor controllerDescriptor,
                Type controllerType)
        {
            var nestedContainer = _container.GetNestedContainer();
            request.RegisterForDispose(nestedContainer);
            return (IHttpController)nestedContainer.GetInstance(controllerType);
        }
    }
}
در اینجا نحوه‌ی پیاده سازی IHttpControllerActivator را توسط StructureMap ملاحظه می‌کنید.
نکته‌ی مهم آن استفاده از NestedContainer آن است. معرفی آن به متد request.RegisterForDispose سبب می‌شود تا کلیه کلاس‌های IDisposable نیز در پایان کار به صورت خودکار رها سازی شده و نشتی حافظه رخ ندهد.


معرفی StructureMapHttpControllerActivator به برنامه

فایل WebApiConfig.cs را گشوده و تغییرات ذیل را در آن اعمال کنید:
using System.Web.Http;
using System.Web.Http.Dispatcher;
using StructureMap;
using WebApiDISample.Core;
using WebApiDISample.Services;

namespace WebApiDISample
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // IoC Config
            ObjectFactory.Configure(c => c.For<IEmailsService>().Use<EmailsService>());

            var container = ObjectFactory.Container;
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator), new StructureMapHttpControllerActivator(container));


            // Web API routes
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}
 در ابتدا تنظیمات متداول کلاس‌ها و اینترفیس‌ها صورت می‌گیرد. سپس نحوه‌ی معرفی  StructureMapHttpControllerActivator را به GlobalConfiguration.Configuration.Services مخصوص Web API ملاحظه می‌کنید. این مورد سبب می‌شود تا به صورت خودکار کلیه وابستگی‌های مورد نیاز یک Web API Controller به آن تزریق شوند.


تهیه سرویسی برای آزمایش برنامه

namespace WebApiDISample.Services
{
    public interface IEmailsService
    {
        void SendEmail();
    }
}

using System;

namespace WebApiDISample.Services
{
    /// <summary>
    /// سرویسی که دارای قسمت دیسپوز نیز هست
    /// </summary>
    public class EmailsService : IEmailsService, IDisposable
    {
        private bool _disposed;

        ~EmailsService()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void SendEmail()
        {
            //todo: send email!
        }

        protected virtual void Dispose(bool disposeManagedResources)
        {
            if (_disposed) return;
            if (!disposeManagedResources) return;

            //todo: clean up resources here ...

            _disposed = true;
        }
    }
}
در اینجا یک سرویس ساده ارسال ایمیل را بدون پیاده سازی خاصی مشاهده می‌کنید.
نکته‌ی مهم آن استفاده از IDisposable در این کلاس خاص است (ضروری نیست؛ صرفا جهت بررسی بیشتر اضافه شده‌است). اگر در کدهای برنامه، یک چنین کلاسی وجود داشت، نیاز است متد Dispose آن نیز توسط IoC Container فراخوانی شود. برای آزمایش آن یک break point را در داخل متد Dispose قرار دهید.


استفاده از سرویس تعریف شده در یک Web API Controller

using System.Web.Http;
using WebApiDISample.Services;

namespace WebApiDISample.Controllers
{
    public class ValuesController : ApiController
    {
        private readonly IEmailsService _emailsService;
        public ValuesController(IEmailsService emailsService)
        {
            _emailsService = emailsService;
        }

        // GET api/values/5
        public string Get(int id)
        {
            _emailsService.SendEmail();
            return "_emailsService.SendEmail(); called!";
        }
    }
}
در اینجا مثال ساده‌ای را از نحوه‌ی تزریق سرویس ارسال ایمیل را در ValuesController مشاهده می‌کنید.
تزریق وهله‌ی مورد نیاز آن، به صورت خودکار توسط StructureMapHttpControllerActivator که در ابتدای بحث معرفی شد، صورت می‌گیرد.

فراخوانی متد Get آن‌را نیز توسط کدهای سمت کاربر ذیل انجام خواهیم داد:
<h2>Index</h2>

@section scripts
{
    <script type="text/javascript">
        $(function () {
            $.getJSON('/api/values/1?timestamp=' + new Date().getTime(), function (data) {
                alert(data);
            });
        });
    </script>
}
درون متد Get کنترلر، یک break point قرار دهید. همچنین داخل متد Dispose لایه سرویس نیز جهت بررسی بیشتر یک break point قرار دهید.
اکنون برنامه را اجرا کنید. هنگام فراخوانی متد Get، وهله‌ی سرویس مورد نظر، نال نیست. همچنین متد Dispose نیز به صورت خودکار فراخوانی می‌شود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
WebApiDISample.zip 
مطالب
نحوه استفاده از Text template ها در دات نت - قسمت دوم
بعد از ایجاد فایل Text template که در جلسه قبل با آن آشنا شدید، برای شروع قواعد زیر را در نظر بگیرید :
- تنظیمات مربوط به فایل Text template و نحوه تولید خروجی در ابتدای فایل و بین علامت <#@ و  #> قرار میگیرد.
- هر متنی که بصورت معمول در فایل tt نوشته شود، به همان صورت در فایل خروجی قرار می‌گیرد.
- هر دستوری که در بین علامت‌های  <#=  و #> قرار گیرد هنگام کامپایل اجرا شده و معادل آن در همان مکان متن قرار میگیرد.
- هر دستوری که بین علامت‌های  <#  و #>  قرار گیرد، هنگام کامپایل اجرا می‌شود. در این صورت دستورات نوشته شده در این قسمت فقط اجرا می‌گردد و معمولا برای استفاده در قسمتهای دیگر، داخل بلوک <#= #> نوشته می‌شود . 
- برای تعریف کلاس یا متد جدید جهت استفاده در فایل tt می‌توانیم کلاس را در بین علامت <#+ و  #> قرار دهیم. در این صورت کلاس و متد‌های نوشته شده در قسمتهای دیگر، داخل بلوک  <#= #> و یا  <# #> مورد استفاده قرار میگیرند.

اجازه دهید با یک مثال ساده قواعد اولیه را بررسی کنیم :  
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>

<# var T = DateTime.Now; #>

The Time is : <#= T #>

The Time is : <#=  DateTime.Now #>
در این مثال، T در واقع متغیری است که در  بلوک <# #> تعریف گردیده و در بلوک <#= #> مقدار آن استفاده میشود. خروجی فایل چیزی شبیه به دو خط زیر خواهد بود:

The Time is : 02/16/2014 14:17:39
The Time is : 02/16/2014 14:17:39
به عنوان یک مثال دیگر که قواعد توضیح داده شده را پوشش دهد به مثال زیر توجه کنید :
<#@ template debug="true" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>

using System;
using System.Text;

<# string ClassName = "DotnetTips"; #>
public class <#= ClassName + "_" + new MyTestClass().Str #>
{
}

<#+
public class MyTestClass
{
public string  Str { get{return new DateTime().DayOfWeek.ToString() ;} }
}
#>
خروجی  Text template   بالا فایل Cs    با  محتوی شبیه کد زیر خواهد بود:  (روز نگارش مطلب البته دوشنبه است) 
using System;
using System.Text;

public class DotnetTips_Monday
{
}

به عنوان یک مثال ساده دیگر برای فهم بیشتر به کد زیر جهت تولید Table  در Html  توجه کنید:  

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".html" #>

<html><body>
<table>
    <# for (int i = 1; i <= 10; i++)
       { #>
         <tr>
 <td>Test name <#= i #> </td>
             <td>Test value <#= i * i #> </td> 
 </tr>
    <# } #>
 </table>
</body></html>
فکر می‌کنم این 3 مثال ساده، تا حد زیادی قواعد اولیه T4 Text Template  را برای شما روشن کرده باشد. در قسمت بعدی برخی قواعد تکمیلی را در این مورد خدمتتون ارائه میدم.
نظرات مطالب
چگونگی گزارشگیری از Business Objects مانند List توسط StimulSoft
با کد زیر
    public class Book
    {
        public string Title { get; set; }
        public int Id { get; set; }
        public Author Author { get; set; }
        public Publisher Publisher { get; set; }

        public IList<Book> GetBooks()
        {
            var books = new List<Book>();

            for (int i = 0; i < 10; i++)
            {
                var book = new Book()
                {
                    Id = i,
                    Title = $"Title {i}",
                    Author = new Author()
                    {
                        FirstName = $"Author {i} First Name",
                        LastName = $"Author {i} Last Name",
                        Person = new Person()
                        {
                            Type = $"Type {i}",
                            Value = $"Value {i}"
                        }
                    },
                    Publisher = new Report.Publisher()
                    {
                        Name = $"Publiser {i}"
                    }
                };
                books.Add(book);
            }
            return books;
        }
    }

    public class Author
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Person Person { get; set; }
    }

    public class Person
    {
        public string Type { get; set; }
        public string Value { get; set; }
    }

    public class Publisher
    {
        public string Name { get; set; }
    }
و فایل mrt زیر مشکلی دیده نشد.