مطالب دوره‌ها
آشنایی با مدل برنامه نویسی TAP
تاریخچه‌ی اعمال غیر همزمان در دات نت فریم ورک

دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی می‌کرده‌است. همچنین این مورد یکی از ویژگی‌های Win32 نیز می‌باشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام می‌شود.
        public string TestNoneAsync()
        {
            var webClient = new WebClient();
            return webClient.DownloadString("http://www.google.com");
        }
در این مثال متداول، متد DownloadString به صورت همزمان یا synchronous عمل می‌کند. به این معنا که تا پایان عملیات دریافت اطلاعات از وب، منتظر مانده و ترد جاری را قفل می‌کند. مشکل از جایی آغاز می‌شود که مدت زمان دریافت اطلاعات، طولانی باشد. چون این عملیات در ترد UI در حال انجام است، کل رابط کاربری برنامه تا پایان عملیات نیز قفل شده و دیگر پاسخگوی سایر اعمال رسیده نخواهد بود. در این حالت عموما ویندوز در نوار عنوان برنامه، واژه‌های Not responding را نمایش می‌دهد.
این مورد همچنین در برنامه‌های سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخ‌دهی سرور نیز کاهش می‌یابد. بنابراین در این نوع موارد، برنامه‌های چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامه‌های سمت سرور ندارند. زیرا در آن‌ها می‌توان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه می‌شود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز می‌باشد. این متد به محض فراخوانی، ترد جاری را آزاد می‌کند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجه‌ی دریافت اطلاعات از وب نمی‌شود. به این نوع API، یک Asynchronous API گفته می‌شود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمی‌شود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف می‌کند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آن‌را پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شده‌است. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی می‌گردد.
        public void TestAsync()
        {
            var webClient = new WebClient();
            webClient.DownloadStringAsync(new Uri("http://www.google.com"));
            webClient.DownloadStringCompleted += webClientDownloadStringCompleted;
        }

        void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // use e.Result
        }
در اینجا همچنین توسط آرگومان DownloadStringCompletedEventArgs، موفقیت یا شکست عملیات نیز گزارش می‌شود و مقدار e.Result حاصل عملیات است.

مشکل!
ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار ساده‌تر است از نمونه‌ی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شده‌است و نتیجه‌ی نهایی، درون یک روال رخدادگردان بدست می‌آید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته می‌شود. EAP در دات نت 2 معرفی شد. روال‌های رخدادگردان در این حالت، در ترد اصلی برنامه اجرا می‌شوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه می‌یابد.


آشنایی با Synchronization Context

ابتدا یک برنامه‌ی WinForms ساده را آغاز کرده و یک دکمه‌ی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آن‌را به نحو ذیل تغییر دهید:
using System;
using System.Linq;
using System.Net;
using System.Windows.Forms;

namespace Async02
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    txtResults.Text = headersText;
                }, null);
        }

        private string formatHeaders(WebHeaderCollection headers)
        {
            var headerString = headers.Keys.Cast<string>()
                                      .Select(header => string.Format("{0}:{1}", header, headers[header]));
            return string.Join(Environment.NewLine, headerString.ToArray());
        }
    }
}
در اینجا از روش دیگری برای دریافت اطلاعات از وب استفاده کرده‌ایم. با استفاده از امکانات HttpWebRequest، کوئری‌های پیشرفته‌تری را می‌توان تهیه کرد. برای مثال می‌توان نوع متد را به HEAD تنظیم نمود؛ تا صرفا مقادیر هدر آدرس درخواستی از سرور، دریافت شوند.
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شده‌است. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجه‌ی عملیات به دست می‌آید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
 An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
علت اینجا است که asyncResult دریافتی، در تردی دیگر نسبت به ترد اصلی برنامه که UI را اداره می‌کند، اجرا می‌شود. یکی از راه حل‌های این مشکل و انتقال اطلاعات به ترد اصلی برنامه، استفاده از Synchronization Context است:
        private void btnGetInfo_Click(object sender, EventArgs e)
        {
            var sync = SynchronizationContext.Current;
            var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            req.Method = "HEAD";
            req.BeginGetResponse(
                asyncResult =>
                {
                    var resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
                    var headersText = formatHeaders(resp.Headers);
                    sync.Post(delegate { txtResults.Text = headersText; }, null);
                }, null);
        }
SynchronizationContext.Current در اینجا چون در ابتدای متد دریافت اطلاعات اجرا می‌شود، به ترد UI، یا ترد اصلی برنامه اشاره می‌کند. به همین جهت این زمینه را نباید داخل Async callback دریافت کرد؛ زیرا ترد جاری آن، ترد UI مدنظر ما نیست. سپس همانطور که ملاحظه می‌کنید، توسط متد Post آن می‌توان اطلاعات را در زمینه‌ی تردی که SynchronizationContext به آن اشاره می‌کند اجرا کرد.


برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل  Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینه‌ی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا می‌شود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کرده‌است، اما باز هم کد ساده‌ای به نظر نمی‌رسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هسته‌ی زبان اضافه شده‌است.


Syntax ابتدایی یک متد Async

در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System;
using System.Threading.Tasks;

namespace Async01
{
    public class AsyncExample
    {
        public async Task DoWorkAsync(int parameter)
        {
            await Task.Delay(parameter);
            Console.WriteLine(parameter);
        }
    }
}
شیوه‌ی نگارش آن بر اساس راهنمای نوشتن برنامه‌های Async یا Task asynchronous programming model یا به اختصار TAP است:
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونه‌ی جنریک آن‌را. البته کامپایلر، async void را نیز پشتیبانی می‌کند ولی در قسمت‌های بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکل‌زا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آ‌ن‌ها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژه‌ی کلیدی async نیز استفاده می‌گردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر می‌گوید، عبارت پس از من، یک وظیفه‌ی غیرهمزمان است و ادامه‌ی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.

در متد DoWorkAsync، ابتدا به اندازه‌‌ای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا می‌شود.


یک اشتباه عمومی! استفاده از واژه‌های کلیدی async و await متد شما را async نمی‌کنند.

برخلاف تصور ابتدایی از بکارگیری واژه‌های کلیدی async و await، این کلمات نحوه‌ی اجرای متد شما را async نمی‌کنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید می‌باشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
        public async Task<double> GetNumberAsync()
        {
            var generator = new Random();
            await Task.Delay(generator.Next(1000));

            return generator.NextDouble();
        }
در این متد با استفاده از Task.Delay، انجام یک عملیات طولانی شبیه سازی شده‌است؛ مثلا دریافت یک عدد یا نتیجه از یک وب سرویس. سپس در نهایت، عددی را بازگشت داده است. برای بازگشت یک خروجی double، در اینجا از نمونه‌ی جنریک Task استفاده شده‌است.
در ادامه برای استفاده از آن خواهیم داشت:
        public async Task<double> GetSumAsync()
        {
            var leftOperand = await GetNumberAsync();
            var rightOperand = await GetNumberAsync();

            return leftOperand + rightOperand;
        }
خروجی این متد تنها زمانی بازگشت داده می‌شود که نتایج leftOperand و rightOperand از وب سرویس فرضی، دریافت شده باشند و در اختیار مصرف کننده قرارگیرند. بنابراین همانطور که ملاحظه می‌کنید از واژه‌ی کلیدی await جهت تشکیل یک عملیات غیرهمزمان و مدیریت ساده‌تر کدهای نهایی، شبیه به کدهای معمولی همزمان استفاده شده‌است.
در کدهای همزمان متداول، سطر اول ابتدا انجام می‌شود و بعد سطر دوم و الی آخر. با استفاده از واژه‌ی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده می‌شد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجه‌ی آن در یک روال رخ‌داد گردان جایی در کدهای برنامه دریافت می‌شد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژه‌ی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه می‌دهد. به این ترتیب بهتر می‌توان بر روی منطق و الگوریتم‌های مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.

امکان استفاده از واژه‌ی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شده‌است:
        public async Task<double> GetProductOfSumAsync()
        {
            var leftOperand = GetSumAsync();
            var rightOperand = GetSumAsync();

            return await leftOperand * await rightOperand;
        }
اگر await را از این مثال حذف کنیم، خطای کامپایل زیر را دریافت خواهیم کرد:
 Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
خروجی متد GetSumAsync صرفا یک Task است و نه یک عدد. پس از استفاده از await، عملیات آن انجام شده و بازگشت داده می‌شود.


اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونه‌ی async سی‌شارپ 5 کنیم، می‌توان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
        public async Task<string> DownloadAsync()
        {
            var webClient = new WebClient();
            return await webClient.DownloadStringTaskAsync("http://www.google.com");
        }
نکته‌ی مهم این کد علاوه بر ساده سازی اعمال غیر همزمان، برای استفاده از نتیجه‌ی نهایی آن، نیازی به SynchronizationContext معرفی شده در تاریخچه‌ی ابتدای بحث نیست. نتیجه‌ی دریافتی از آن در ترد اصلی برنامه تحویل داده شده و به سادگی قابل استفاده است.


سؤال: آیا استفاده از await نیز ترد جاری را قفل می‌کند؟

اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر می‌رسد که در اینجا نیز ترد اصلی، همانند قبل قفل شده‌است.
        public void TestDownloadAsync()
        {
            Debug.WriteLine("Before DownloadAsync");
            DownloadAsync();
            Debug.WriteLine("After DownloadAsync");
        }
اگر این متد را اجرا کنید (در آن await بکار نرفته)، بلافاصله خروجی ذیل را مشاهده خواهید کرد:
 Before DownloadAsync
After DownloadAsync
به این معنا که در اصل، همانند سایر روش‌های async موجود از دات نت یک، در اینجا نیز فراخوانی متد async ترد اصلی را بلافاصله آزاد می‌کند و ترد آن‌را قفل نخواهد کرد. استفاده از await نیز عملکرد کدها را تغییر نمی‌دهد. تنها کامپایلر در پشت صحنه همان کدهای لازم جهت مدیریت روال‌های رخدادگردان و callbackها را تولید می‌کند، به نحوی که صرفا نحوه‌ی کدنویسی ما همزمان به نظر می‌رسد، اما در پشت صحنه، نحوه‌ی اجرای آن غیرهمزمان است.


برنامه‌های Async و نگارش‌های مختلف دات نت

شاید در ابتدا به نظر برسد که قابلیت‌های جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژه‌های کلیدی را پشتیبانی کند، امکان استفاده از آن‌ها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامه‌های دات نت 4 البته):
 PM> Install-Package Microsoft.Bcl.Async
این روال متداول VS.NET بوده است تا به امروز. برای مثال اگر VS 2010 را نصب کنید و سپس یک برنامه‌ی دات نت 3.5 را ایجاد کنید، امکان استفاده‌ی کامل از تمام امکانات سی‌شارپ 4، مانند آرگومان‌های نامدار و یا مقادیر پیش فرض آرگومان‌ها را در یک برنامه‌ی دات نت 3.5 نیز خواهید داشت. همین نکته در مورد async نیز صادق است. VS 2012 (یا نگارش‌های جدیدتر) را نصب کنید و سپس یک پروژه‌ی دات نت 4 را آغاز کنید. امکان استفاده از async و await را خواهید داشت. البته در این حالت دسترسی به متدهای الحاقی جدید را مانند DownloadStringTaskAsync نخواهید داشت. برای رفع این مشکل باید بسته‌ی  Microsoft.Bcl.Async را نیز توسط نیوگت نصب کنید.
مطالب
استفاده از DataAnnotations در EF DataBase First
همانطور که میدانیم DataAnnotations برای فیلد‌های مدل‌ها در MVC وقتی که از EF Code First استفاده کنیم کار ما را برای اعتبارسنجی بسیار ساده میکنند. اما وضع در EF Database First متفاوت است زیرا اگر مدلی را که برنامه برایمان میسازد را به روز کنیم ( توسط Updata Model) تمام اعتبارسنجی هایی که نوشته بودیم پاک شده و مدل خام دوباره برای ما تولید میشود . برای رفع این مشل باید از PartialClass استفاده نماییم تا بتوانیم همیشه اعتبارسنجی‌ها را داشته باشیم :

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    [MetadataType(typeof(MovieMetaData))]
    public partial class Movie
    {
    }


    public class MovieMetaData
    {
        [Required]
        public object Title { get; set; }

        [Required]
        [StringLength(5)]
        public object Director { get; set; }


        [DisplayName("Date Released")]
        [Required]
        public object DateReleased { get; set; }
    }

}

لینک مطلب اصلی : http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validation-with-the-data-annotation-validators-cs

اشتراک‌ها
5 نکته برای بهبود کیفیت کدهای برنامه‌ای که از یک ORM استفاده می‌کند
 شرح حال واقعی پروژه‌ای است که از NHibernate استفاده کرده و دست آخر به برنامه‌ای ختم شده با مصرف RAM نزدیک به 2 گیگابایت. علت اصلی آن: استفاده از الگوی مخزن متداول. این الگو در نهایت سبب می‌شود تا از بسیاری از توانایی‌های مطلوب و پیشرفته ORM خود نتوانید استفاده کنید و با «توهم» قابل تعویض بودن لایه Repository با ORM دیگری، برنامه‌ای بسیار بی‌کیفیت را تولید نمائید.  
5 نکته برای بهبود کیفیت کدهای برنامه‌ای که از یک ORM استفاده می‌کند
مطالب دوره‌ها
مثال - نمایش درصد پیشرفت عملیات توسط SignalR
برنامه‌های وب در سناریوهای بسیاری نیاز دارند تا درصد پیشرفت عملیاتی را به کاربران گزارش دهند. نمونه ساده آن، گزارش درصد پیشرفت میزان دریافت یک فایل است و یا اعلام درصد انجام یک عملیات طولانی از سمت سرور به کاربر. در ادامه قصد داریم این موضوع را توسط SignalR پیاده سازی کنیم.


نکته‌ای در مورد نگارش‌های مختلف SignalR
اگر برنامه شما قرار است دات نت 4 را پشتیبانی کند، آخرین نگارش SignalR که با آن سازگار است، نگارش 1.1.3 می‌باشد. بنابراین اگر دستور ذیل را اجرا کنید:
 PM> Install-Package Microsoft.AspNet.SignalR
SignalR 2 را نصب می‌کند که با دات نت 4 و نیم به بعد سازگار است.
اگر دستور ذیل را اجرا کنید، SiganlR 1.x را نصب می‌کند که با دات نت 4 به بعد سازگار است:
 PM> Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
پیش فرض این مطلب نیز استفاده از نگارش 1.1.3 می‌باشد تا بازه بیشتری از وب سرورها را شامل شود.
با اینکار Microsoft.AspNet.SignalR.JS نیز به صورت خودکار نصب می‌گردد و به این ترتیب کلاینت جاوا اسکریپتی SiganlR نیز در برنامه قابل استفاده خواهد بود.


تنظیمات فایل Global.asax.cs

سطر فراخوانی متد RouteTable.Routes.MapHubs باید در ابتدای متد Application_Start فایل Global.asax.cs قرار گیرد (پیش از هر تنظیم دیگری). تفاوتی هم نمی‌کند که برنامه وب فرم است یا MVC. به این ترتیب مسیریابی‌های SignalR تنظیم شده و مسیر http://localhost/signalr/hubs قابل استفاده خواهد بود.


تنظیمات اسکریپت‌های سمت کلاینت مورد نیاز

پس از نصب بسته SignalR، سه اسکریپت ذیل باید به ابتدای صفحه وب اضافه شوند تا کلاینت‌های جاوا اسکریپتی SignalR بتوانند با سرور ارتباط برقرار کنند:
 <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.signalR-1.1.3.min.js" type="text/javascript"></script>
<script src="signalr/hubs" type="text/javascript"></script>
این تنظیمات نیز برای هر دو نوع برنامه‌های وب فرم و MVC یکسان است.


تعریف کلاس Hub برنامه

using Microsoft.AspNet.SignalR;

namespace WebFormsSample03.Common
{
    public class ProgressHub : Hub
    {
        /// <summary>
        /// این متد استاتیک تعریف شده تا در برنامه به صورت مستقیم قابل استفاده باشد
        /// یا می‌شد اصلا این متد تعریف نشود و از همان دریافت زمینه هاب در کنترلر استفاده گردد
        /// </summary>        
        public static void UpdateProgressBar(int value, string connectionId)
        {
            var ctx = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
            ctx.Clients.Client(connectionId).updateProgressBar(value); //فراخوانی یک متد در سمت کلاینت
        }
    }
}
متدی که در کلاس هاب برنامه تعریف شده، از نوع استاتیک است. از این جهت که می‌خواهیم این متد را در خارج از این هاب و در یک کنترلر Web API فراخوانی کنیم. زمانیکه متدی به صورت استاتیک تعریف می‌شود، ارتباط آن با وهله جاری کلاس یا this قطع خواهد شد. به همین جهت نیاز است تا از طریق متد GlobalHost.ConnectionManager.GetHubContext مجددا به context کلاس هاب دسترسی پیدا کنیم.
البته تعریف این متد در اینجا ضروری نبود. حتی می‌شد بدنه کلاس هاب را خالی تعریف کرد و متد GetHubContext را مستقیما داخل یک کنترلر فراخوانی نمود.
متد UpdateProgressBar، مقدار value را به تنها یک کلاینت که Id آن مساوی connectionId دریافتی است، ارسال می‌کند. این کلاینت باید یک callback جاوا اسکریپتی را جهت تامین متد پویای updateProgressBar تدارک ببیند.


کلاس Web API کنترلر دریافت فایل‌ها

فرقی نمی‌کند که برنامه شما از نوع وب فرم است یا MVC. امکانات Web API در هر دو نوع پروژه، قابل دسترسی است (همان ایده یک ASP.NET واحد).
بنابراین نیاز است یک کنترلر وب API جدید را به پروژه اضافه کرده و محتوای آن را به شکل ذیل تغییر دهیم:
using System.Threading;
using System.Web.Http;
using WebFormsSample03.Common;

namespace WebFormsSample03
{
    public class DownloadRequest
    {
        public string Url { set; get; }
        public string ConnectionId { set; get; }
    }

    public class DownloaderController : ApiController
    {
        public void Post([FromBody]DownloadRequest data)
        {
            //todo: start downloading the data.Url ....

            ProgressHub.UpdateProgressBar(10, data.ConnectionId);
            Thread.Sleep(2000);

            ProgressHub.UpdateProgressBar(40, data.ConnectionId);
            Thread.Sleep(3000);

            ProgressHub.UpdateProgressBar(64, data.ConnectionId);
            Thread.Sleep(2000);

            ProgressHub.UpdateProgressBar(77, data.ConnectionId);
            Thread.Sleep(2000);

            ProgressHub.UpdateProgressBar(92, data.ConnectionId);
            Thread.Sleep(3000);

            ProgressHub.UpdateProgressBar(99, data.ConnectionId);
            Thread.Sleep(2000);

            ProgressHub.UpdateProgressBar(100, data.ConnectionId);
        }
    }
}
اگر برنامه شما وب فرم است، باید تنظیمات مسیریابی ذیل را نیز به آن افزود. در برنامه‌های MVC4 این تنظیم به صورت پیش فرض وجود دارد:
using System;
using System.Web.Http;
using System.Web.Routing;

namespace WebFormsSample03
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            // Register the default hubs route: ~/signalr
            RouteTable.Routes.MapHubs();

            RouteTable.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}
کاری که در این کنترلر انجام شده، شبیه سازی یک عملیات طولانی توسط متد Thread.Sleep است. همچنین این کنترلر، id کلاینت درخواست کننده یک url را نیز دریافت می‌کند. بنابراین می‌توان به نحو بهینه‌ای، تنها نتایج پیشرفت عملیات را به این کلاینت ارسال کرد و نه به سایر کلاینت‌ها.
همچنین در اینجا با توجه به مسیریابی تعریف شده، باید اطلاعات را به آدرس api/Downloader از نوع Post ارسال کرد.


تعریف کلاینت متصل به Hub

در سمت سرور، متد پویای updateProgressBar فراخوانی شده است. اکنون باید این متد را در سمت کلاینت پیاده سازی کنیم:
    <form id="form1" runat="server">
    <div>
    <input id="txtUrl" value="http://www.site.com/file.rar" type="text" />
        <input id="send" type="button" value="start download ..." />
        <br />
        <div id="bar" style="border: #000 1px solid; width:300px;"></div>
    </div>
    </form>
    <script type="text/javascript">
        $(function () {
            $.connection.hub.logging = true; //اطلاعات بیشتری را در جاوا اسکریپت کنسول مرورگر لاگ می‌کند
            var progressHub = $.connection.progressHub; //این نام مستعار پیشتر توسط ویژگی نام هاب تنظیم شده است
            progressHub.client.updateProgressBar = function (value) {
                //متدی که در اینجا تعریف شده دقیقا مطابق نام متد پویایی است که در هاب تعریف شده است
                //به این ترتیب سرور می‌تواند کلاینت را فراخوانی کند
                $("#bar").html(GaugeBar.generate(value));
            };
            $.connection.hub.start() // فاز اولیه ارتباط را آغاز می‌کند
            .done(function () {
                $("#send").click(function () {
                    $("#send").attr('disabled', 'disabled');
                    var myClientId = $.connection.hub.id;
                    // اکنون اتصال برقرار است به سرور
                    $.ajax({
                        type: "POST",
                        contentType: "application/json",
                        url: "/api/Downloader",
                        data: JSON.stringify({ Url: $("#txtUrl").val(), ConnectionId: myClientId })
                    }).success(function () {
                        $("#send").removeAttr('disabled');
                    }).fail(function () {
                        //                    
                    });
                });
            });
        });
    </script>
بر روی این فرم، یک جعبه متنی که Url را دریافت می‌کند و یک دکمه‌ی آغاز کار دریافت این Url، وجود دارد.
در ابتدای کار صفحه، اتصال به progressHub برقرار می‌شود. اگر دقت کنید، نام این هاب با حروف کوچک در اینجا (در سمت کلاینت) آغاز می‌گردد.
سپس با تعریف یک callback به نام progressHub.client.updateProgressBar، پیام‌های دریافتی از طرف سرور را به یک افزونه progress bar جی‌کوئری، برای نمایش ارسال می‌کند.
کار اتصال به رویداد کلیک دکمه‌ی آغاز دریافت فایل، در متد done باید انجام شود. این callback زمانی فراخوانی می‌گردد که کار اتصال به سرور با موفقیت صورت گرفته باشد.
سپس در ادامه توسط jQuery Ajax، اطلاعات Url و همچنین Id کلاینت را به مسیر api/Downloader یا همان web api controller ارسال می‌کنیم.



کدهای کامل این مثال را از اینجا نیز می‌توانید دریافت نمائید:
  WebFormsSample03.zip
مطالب
کار با اسناد در RavenDb 4، بازیابی اسناد
در قسمت قبل عملیات ثبت و ویرایش اسناد را بررسی کردیم. همچنین نحوه‌ی کار متد LoadAsync (و یا Load) را دیدیم. برای بازیابی یک سند، به همرا اسناد مرتبط با آن، از Load به همراه متد Include استفاده می‌کنیم.
در این مثال میخواهیم آدرس شخص مورد نظر در برنامه با کد 59 بازیابی شود.
var user = _documentSession
    .Include<User>(x => x.Apps[59].AddressId)
    .Load("Users/131-A");
var address = _documentSession.Load<Address>(user.Apps[59].AddressId)

و در صورتیکه بخواهیم تمام آدرس‌های او در تمام برنامه‌های ثبت شده را داشته باشیم، به کد زیر می‌رسیم:
var user = _documentSession
    .Include<User>(x => x.Apps.Values.Select(app => app.AddressId))
    .Load("Users/131-A");
var addresses = List<Address>();
foreach(app in user.Apps)
{
    addresses.Add(_documentSession.Load<Address>(app.AddressId)); //query‌سمت کلاینت انجام اجرا می‌شود
}

 متد Load بسیار سریع کل سند ما را بازیابی میکند اما:
  • حتما باید Id سند(ها) را داشته باشیم.
  • کل سند را بازیابی میکند.
برای رفع این دو مشکل میتوانیم از امکانات Query نویسی در RavenDb استفاده کنیم. به دلیل ذخیره سازی (ظاهرا) فله‌ای اطلاعات در NoSqlها، Query گرفتن از حجم بسیار زیاد این اطلاعات، کار زمان بری است و اجرای Query بدون Index گذاری، کار بیهوده‌ای می‌شود. به همین دلیل با هر Query که اجرا می‌شود، به صورت خودکار یک Index برای آن توسط RavenDb ایجاد شده و Query بر روی Index ایجاد شده، اجرا می‌شود. عملیات Index کردن اطلاعات بصورت اتوماتیک در اولین بار اجرای Query با توجه به حجم داده‌ها می‌تواند بسیار کند باشد. همچنین ما کنترلی بر روی مدیریت ایندکس‌های ایجاد شده نداریم.
Queryها در RavenDb به چند صورت نوشته می‌شوند:

Query
متد Query برای ایجاد Query با استفاده از Linq کاربرد دارد. به مثال زیر توجه کنید:
List<User> users = await _documentSession
    .Query<Users>()
    .Where(u => u.PhoneNumber.StartsWith("915"))
    .ToListAsync();
اجرای Query بالا ابتدا باعث ایجاد یک Index بر روی ویژگی PhoneNumber می‌شود و سپس لیست کاربران را بر می‌گرداند.
برای بازیابی اطلاعات کاربران یک برنامه میتوانیم از Dictionary خود Query بگیریم:
var users = await _documentSession.Query<AppUser>()
    .Where(u => u.Id.Equals("915"))
    .Select(u => new
    {
        u.Apps[appCode].FirstName,
        u.Apps [appCode].LastName,
    })
    .ToListAsync();
این Query در RQL که زبان پرس و جوی مخصوص RavenDb است، چیزی شبیه کد زیر می‌شود:
from Users as user
where startsWith(user.PhoneNumber, "915")
select  {
    FirstName : user.Apps ["59"].FirstName,
    LastName : user.Apps ["59"].LastName
}
مشکلی که در این Query وجود دارد این‌است که کاربرانی که شماره تماس آن‌ها با 915 شروع شده است ولی در برنامه‌ای با کد 59 ثبت نشده‌اند هم در Query بازگشت داده می‌شوند و مقادیر بازگشتی برای فیلدها هم null خواهد بود. اگر بجای ذکر صریح عبارت u. Apps [appCode].FirstName به صورت زیر عمل کنیم:
from u in _documentSession.Query<User>()
                where u.PhoneNumber.StartsWith("915")
                let app = u.Apps["59"]
                select new
                {
                    app.FirstName,
                    app.LastName,
                };
عبارت let app = u.Apps["59"] در RQL تبدیل به یک متد جاوااسکریپتی می‌شود و به کدی شبیه به کد زیر می‌رسیم:
declare function output(u) {
var app = u.Apps["59"];
return { FirstName : app.FirstName, LastName : app.LastName};
}
from Users as user
where startsWith(user.PhoneNumber, "915")
select output(user)
حالا میتوانیم Key مورد نظر در دیکشنری را هم در Query به شکل زیر دخیل کنیم:
app.FirstName,
app.LastName,
*key = u.ActiveInApps.Select(a => a.Key)
و در ادامه با استفاده از متد Search، این فیلد را که به کلید دیکشنری اشاره می‌کند، محدود کرده و بعد از آن Query خود را اجرا میکنیم:
query = query.Search(u => u.key, "59");
در صورتیکه بجای دیکشنری از آرایه استفاده کرده باشیم هم کدهای ما به همین صورت می‌باشد با کمی تغییرات مربوط به تفاوت List و Dictionary!
اما هنوز Query ما بدرستی کار نمیکند چرا که ویژگی Key در RavenDb ایندکس نشده‌است و نمیتواند این ایندکس را هم تشخیص دهد. دلیل آن هم این است که تنها ویژگی‌هایی که در مرتب سازی (Sort) و یا فیلتر مورد استفاده قرار گیرند، به ایندکس‌ها اضافه می‌شوند. برای حل این مشکل باید بصورت دستی Index خود را در RavenDb بسازیم. این کار با ارث بری از کلاس پایه‌ی AbstractIndexCreationTask شروع می‌شود و مدلی را که میخواهیم Index بر روی آن اعمال شود نیز ذکر میکنیم و بعد از آن در سازنده‌ی کلاس، Index خود را می‌سازیم:
public class User_MyIndex : AbstractIndexCreationTask<User>
{
    Map = users => 
                           from u in users
                           from app in u.Apps
                           select new
                           {
                                 Id = u.Id,
                                 PhoneNumber = u.PhoneNumber,
                                 UserName = app.Value.UserName,
                                 FirstName = app.Value.FirstName,
                                 LastName = app.Value.LastName,
                                 IsActive = app.Value.IsActive,
                                 key = app.Key
     };
}
در این ایندکس به ازای هر کاربر، تمام برنامه‌هایی که ثبت شده، بررسی شده و ایندکس می‌شوند. نکته‌ای که باید به آن توجه کنید این است که ویژگی‌های ذکر شده فقط به RavenDb نحوه‌ی بازیابی فیلدهای سند را برای Index گذاری می‌گوید و همچنان خروجی این Index از نوع User بوده و تمام سند را بازگشت میدهد و باید از متد Select در صورت نیاز استفاده کنیم. برای اعمال این ایندکس به سمت سرور از متد:
new User_MyIndex().Execute(store);
و برای ارسال چندین Index به سمت سرور از متد:
IndexCreation.CreateIndexes(typeof(User_MyIndex).Assembly, store);
استفاده می‌کنیم. اکنون اگر به Query خود این ایندکس را معرفی کنیم، خروجی ما به‌درستی فقط کاربران برنامه مورد نظر را بر می‌گرداند:
from u in _documentSession.Query<User, User_MyIndex>() ...
کلاس AbstractIndexCreationTask متدهای زیادی برای کنترل دقیق Indexها در اختیار ما قرار میدهد که پرکاربردترین آن‌ها میتوانند متدهای زیر باشند: 
Index : نحوه‌ی Index کردن هر یک از پراپرتی‌ها را مشخص می‌کند.
Store : برای مواقعی کاربرد دارد که شما می‌خواهید مقدار Index شده را برای دسترسی سریع‌تر همرا با Index ذخیره کنید.
LoadDocument: این متد Id یا لیستی از Idها را به عنوان ورودی گرفته و سند مورد نظر را بازیابی می‌کند. زمانیکه میخواهیم اسناد مرتبط را همراه با سند، Index کنیم کاربرد دارد. برای مثال وقتی میخواهیم Addressهای کاربر را که در سندی جداگانه قرار دارند، به همراه اطلاعات او در Index شرکت دهیم:
select new
{
      ...
      key = aia.Key,
      Address = LoadDocument<Address>(aia.Value.AddressId),
      // City = LoadDocument<Address>(aia.Value.AddressId).City,
};
و برای Indexکردن لیستی از اسناد مرتبط به صورت زیر از LoadDocument استفاده میکنیم:
Message = app.Messages.Select(m => LoadDocument<Message>(m).Content)
* زمانی که میخواهید کلید یک Dictionary را Index کنید و میخواهید نام فیلد آن را key قرار دهید باید از k کوچک استفاده کنید؛ چرا که Key، جزء کلمات رزرو شده‌ی RavenDb می‌باشد.

DocumentQuery
دسترسی بیشتری را بر روی Query ارسالی به سمت سرور به ما می‌دهد؛ اما  strongly typed  نیست. برای مثال Query بالا را به این صورت میتوانیم با DocumentQuery پیاده کنیم:
var users = _documentSession.Advanced.AsyncDocumentQuery<User, User_MyIndex>()
      .WhereStartsWith(nameof(AppUser.PhoneNumber), "915")
      .WhereEquals("key", appCode, exact: true)
      .SelectFields<AppUserModel>(new[] { $"Apps[{appCode}].FirstName", $"Apps[{appCode}].LastName" })
      .ToListAsync();
متدهای DocumentQuery بسیار متنوع هستند و میتوانید لیست آن‌ها را در اینجا مشاهده کنید.

MoreLikeThis (اسناد شبیه)
از رایج‌ترین کارهایی که در وب سایت‌های مطرح دیده می‌شود نمایش مطالب مرتبط با مطلب جاری می‌باشد و از آنجایی که RavenDb از Lucene.NET برای ایندکس کردن اسناد استفاده می‌کند، میتواند براحتی از MoreLikeThis موجود در پروژه‌ی Contrib آن استفاده نماید.
مدل زیر را در نظر بگیرید:
public class Post
    {
        public int Id { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public List<string> Tags { get; set; }
        public string WriterName { get; set; }
        public string WriterId { get; set; }
    }
برای استفاده از MoreLikeThis باید ابتدا محتویات مطلب خود را با استفاده از StandardAnalyzer ایندکس گذاری کنیم. همانطور که گفته شد، برای Index کردن یک سند از کد زیر میتوانیم استفاده کنیم. با این تفاوت که نحوه‌ی آنالیز سند را نیز مشخص میکنیم:
public class Post_ByContent : AbstractIndexCreationTask<Post>
{
    public Post_ByContent()
    {
        Map = posts=> from post in posts
                      select new
                      {
                          post.Content
                      };

        Analyzers.Add(p => p.Content, "StandardAnalyzer");
    }
}
از این ایندکس در Query به همراه متد MoreLikeThis استفاده میکنیم:
List<Post> posts = _documentSession
    .Query<Post, Post_ByContent>()
    .MoreLikeThis(builder => builder
        .UsingDocument(p => p.Id == "posts/59-A")
        .WithOptions(new MoreLikeThisOptions
        {
            Fields = new[] { nameof(Post.Content) },
            StopWordsDocumentId = "appConfig/StopWords"
        }))
    .ToList();
ابتدا سندی را که میخواهیم اسناد شبیه به آن بازیابی شود، معرفی میکنیم. به اینصورت بررسی بر روی تمام فیلدهای Indexگذاری شده اعمال می‌شود. اگر بخواهیم تنظیماتی را به متد اضافه کنیم از MoreLikeThisOptions استفاده میکنیم. حداقل تنظیمات میتواند معرفی نام فیلد مورد نظر برای کاهش بار سرور و همچنین معرفی سندی که StopWordهای ما در آن قرار دارد، باشد. می‌توانید در مورد StopWordها و کاربرد آن در Lucene از این مقاله استفاده کنید. 
مطالب
MongoDb در سی شارپ (بخش هشتم)
در الگوهایی که به عنوان واسط بین اپلیکیشن و دیتابیس تعریف میکنیم نام دو الگوی Repository و Unit of work به چشم میخورد. در این سایت بارها این مباحث به صورت گفتمان و مقالات تکرار شده‌اند و میدانیم که این الگوها کمک شایانی برای بالا بردن کارآیی برنامه، عدم تکرار کد، قابلیت استفاده مجدد و راحتی کار برای آزمون‌های واحد و چهارچوب‌های تقلید میکنند.

Unit of Work یا الگوی کار در واقع یک الگو، جهت جمع آوری عملیات کار با دیتابیس است که همه عملیات را تحت یک تراکنش به سمت دیتابیس ارسال میکند تا مبحث Atomic بودن عملیات، به مرحله اجرا گذاشته شود. در صورتیکه یکی از عملیات با نقص یا خطایی روبرو شود، کل عملیات Roll back یا برگشت میخورد. از آنجا که دیتابیس‌های معدودی چون Ravendb این مراحل را تا حدی پیاده سازی میکنند نباید از مونگو هم چنین انتظاری نداشته باشید. مونگو برخورد تراکنشی یا اتمیک ندارد؛ پس پیاده سازی الگوی واحد کاری تاثیری بر روی روند کاری آن ندارد. هر چند تعدادی مثال بدین شکل پیاده شده‌اند، ولی در عمل حقیقی نیستند و تنها یک حرکت مشابه داشته‌اند.

ولی الگوی repository  برای پرهیز از تکرار کد، قابلیت به روزرسانی کد و همچنین عملیاتی چون آزمون‌های واحد و چهارچوب تقلید به کار میرود. وابستگی بین اشیاء را کاهش داده و باعث ایجاد یک کد با دوام‌تر میگردد.

ابتدا قبل از هر چیزی نیاز است تا اتصالات یا ساخت کانکشن به سرور و همچنین دریافت دیتابیس مورد نظر را در قالب یک کلاس تعریف نماییم. نام آن را MongoDbContext میگذارم:
   public class MongoDbContext : IMongoDbContext
    {
        public const string DatabaseName = "MongoDbTest";

        private static readonly IMongoClient _client;
        private static readonly IMongoDatabase Database;

        static MongoDbContext()
        {
            _client = new MongoClient();
            Database = _client.GetDatabase(DatabaseName);
        }

        public IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return Database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s");
        }
    }
در حالت بالا شما میتوانید در سازنده کلاس اتصال را برقرار کرده و دیتابیس را دریافت نمایید و از متد GetCollection در سطوح بالاتر، نوع کالکشن درخواستی خود را اعلام کنید. اگر به خط اول کلاس دقت نمایید میبینید که ما از اینترفیسی به نام IMongoDbContext که شامل خطوط زیر میباشد استفاده کردیم و دلیل استفاده این است که اگر قرار باشد از کلاس کانتکست، در کلاس‌های repository استفاده شود، ایجاد وابستگی میکند. چرا که معلوم نیست این کانتکست دقیقا چیست و از کجا آمده است و در آزمون واحد و همچنین تقلید دست ما را می‌بندد و الگوی repository را مردود اعلام میکند. پس از این حیث یک اینترفیس با محتوای زیر تولید کرده‌ایم که از این پس از آن در کدها استفاده میکنیم و پر کردن این اینترفیس‌ها را از طریق تزریق وابستگی‌ها در حالت Constructor Injection که ساده‌ترین نوع آن است انجام میدهیم:
public interface IMongoDbContext
    {    
        IMongoCollection<TEntity> GetCollection<TEntity>();
    }
در صورتیکه دوست دارید محتوای‌های دیگری را چون کانکشن استرینگ و .. نیز در اینجا بگنجانید، به عهده خود شماست. تنها نیاز به تغییراتی کوچک است.
در مرحله بعد یک IMongoDbRepositry ساخته و محتوای آن را به شکل زیر پر میکنیم:
public interface IMongoDbRepository
{
Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new();
}
در اینجا کلاس‌ها را از نوع جنریک تعریف میکنیم تا کاربر بتواند هر نوع کلاسی را که نیاز دارد، به سمت این مخزن ارسال کند. در پیاده سازی هم به شکل زیر آن را تعریف میکنیم:
public class MongoRepository : IMongoDbRepository
    {

        private IMongoDbContext _mongoDbContext ;
        public MongoRepository(IMongoDbContext mongoDbContext)
        {
            _mongoDbContext = mongoDbContext;
        }
 public async Task<List<TEntity>> GetMany<TEntity>(FilterDefinition<TEntity> filter) where TEntity : class, new()
        {            
                var collection = GetCollection<TEntity>();
                var entities = await collection.Find(filter).ToListAsync();                
                return entities;
        }

 private IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return _mongoDbContext.GetCollection<TEntity>();
        } 
 }
همانطور که می‌بینید در ابتدا در سازنده از طریق یک کتابخانه‌ی تزریق وابستگی‌ها (که در اینجا من از Structure map استفاده کرده‌ام) شیء ImongoDbContext را مقدار دهی میکنیم. الان اگر در اینجا بجای تعریف اینترفیس، از همان کلاس مستقیما استفاده میکردیم، بین دو لایه repository و context یک وابستگی ایجاد میشد. ولی در اینجا کانتکست میتواند هر چیزی باشد. بعد از آن به تعریف متد مورد نظر پرداخته‌ایم. البته با توجه به اینکه این تنها یک مثال است، بنده تنها یکی از این متدها را به عنوان نمونه نشان داده‌ام و میتوانید فایل‌های کامل آن را در انتهای مقاله دریافت نمایید. همانطور که مشاهده میکنیم، متدها به صورت غیرهمزمان نوشته شده‌اند که باعث مقیاس پذیری برنامه میشوند و در اینجا از متدهای همزمان استفاده نکرده‌ایم؛ چرا که افرادی که از دیتابیس‌های غیر رابطه‌ای استفاده میکنند، نیاز به مقیاس پذیری بالایی دارند. به همین دلیل نیاز چندانی به استفاده از متدهای همزمان دیده نمیشود. ولی خودتان در صورت تمایل میتوانید آن‌ها را به اینترفیس اضافه کنید. در ضمن در کد بالا متد خصوصی را جهت دریافت کالکشن نوشته‌ایم تا دریافت کالکشن را در کدها، تا حدی خلاصه‌تر و شیواتر کنیم.

الگوی بالا در یک کنترلر به شرح زیر استفاده شده است:
 public class HomeController : Controller
    {
        private IMongoDbRepository _mongoDbRepository;

        public HomeController(IMongoDbRepository mongoDbRepository)
        {
            this._mongoDbRepository = mongoDbRepository;
        }
        // GET: Home
        public async Task<ActionResult> Index()
        {
            var filter = Builders<Resturant>.Filter.Gte("Capacity", 400);
            var c =await _mongoDbRepository.GetMany<Resturant>(filter);       
            return View(c);
        }
    }
در کد بالا رستورانهایی را که 400 نفر یا بیشتر ظرفیت پذیرایی دارند، واکشی کرده و در ویوو نشان میدهد. در اینجا الگوی repo، توسط تزریق وابستگی‌ها ساخته شده و کانتکست آن‌ها به همین شکل ساخته خواهد شد و در کل کنترلر، قابلیت استفاده را دارند.

  MongoRepository.zip
مطالب
انجام پی در پی اعمال Async به کمک Iterators - قسمت اول

تقریبا تمام اعمال کار با شبکه در Silverlight از مدل asynchronous programming پیروی می‌کنند؛ از فراخوانی یک متد وب سرویس تا دریافت اطلاعات از وب و غیره. اگر در سایر فناوری‌های موجود در دات نت فریم ورک برای مثال جهت کار با یک وب سرویس هر دو متد همزمان و غیرهمزمان در اختیار برنامه نویس هستند اما اینجا خیر. اینجا فقط روش‌های غیرهمزمان مرسوم هستند و بس. خیلی هم خوب. یک چارچوب کاری خوب باید روش استفاده‌ی صحیح از کتابخانه‌های موجود را نیز ترویج کند و این مورد حداقل در Silverlight اتفاق افتاده است.
برای مثال فراخوانی‌های زیر را در نظر بگیرید:
private int n1, n2;

private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}

private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}

private void ThirdCall(int number)
{
n2 = number;
// etc
}
عموما در اعمال Async پس از پایان عملیات در تردی دیگر، یک متد فراخوانی می‌گردد که به آن callback delegate نیز گفته می‌شود. برای مثال توسط این سه متد قصد داریم اطلاعاتی را از یک وب سرویس دریافت و استفاده کنیم. ابتدا FirstCall فراخوانی می‌شود. پس از پایان کار آن به صورت خودکار متد SecondCall فراخوانی شده و این متد نیز یک عملیات Async دیگر را شروع کرده و الی آخر. در نهایت قصد داریم توسط مقادیر بازگشت داده شده منطق خاصی را پیاده سازی کنیم. همانطور که مشاهده می‌کنید این اعمال زیبا نیستند! چقدر خوب می‌شد مانند دوران synchronous programming (!) فراخوانی‌های این متدها به صورت ذیل انجام می‌شد:
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
در برنامه نویسی متداول همیشه عادت داریم که اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممکن است ابتدا C انجام شود، سپس A و بعد B یا هر حالت دیگری صرفنظر از تقدم و تاخر آن‌ها در حین معرفی متدهای مرتبط در یک قطعه کد. همچنین میزان خوانایی این نوع کدنویسی نیز مطلوب نیست. مانند مثال اول ذکر شده، یک عملیات به ظاهر ساده به چندین متد منقطع تقسیم شده است. البته به کمک lambda expressions مثال اول را به شکل زیر نیز می‌توان در طی یک متد ارائه داد اما اگر تعداد فراخوانی‌ها بیشتر بود چطور؟ همچنین آیا استفاده از عدد n2 بلافاصله پس از عبارت ذکر شده مجاز است؟ آیا عملیات واقعا به پایان رسیده و مقدار مطلوب به آن انتساب داده شده است؟
private void FetchNumbers()
{
int n1, n2;

Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}

به عبارتی می‌خواهیم کل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلی برنامه را قفل نکنند) اما پی در پی انجام شوند تا مدیریت آن‌ها ساده‌تر شوند (هر لحظه دقیقا بدانیم که کجا هستیم) و همچنین کدهای تولیدی نیز خواناتر باشند.
روش استانداری که توسط الگوهای برنامه نویسی برای حل این مساله پیشنهاد می‌شود، استفاده از الگوی coroutines است. توسط این الگو می‌توان چندین متد Async را در حالت معلق قرار داده و سپس در هر زمانی که نیاز به آن‌ها بود عملیات آن‌ها را از سر گرفت.
دات نت فریم ورک حالت ویژه‌ای از coroutines را توسط Iterators پشتیبانی می‌کند (از C# 2.0 به بعد) که در ابتدا نیاز است از دیدگاه این مساله مروری بر آن‌ها داشته باشیم. مثال بعد یک enumerator را به همراه yield return ارائه داده است:

using System;
using System.Collections.Generic;
using System.Threading;

namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}

static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}

static void Main()
{
printAll();
}
}
}

کامپایلر سی شارپ در عمل یک state machine را برای پیاده سازی این عملیات به صورت خودکار تولید خواهد کرد:

private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;

case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;

case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;

case 3:
this.<>1__state = -1;
break;
}
return false;
}

در حین استفاده از یک IEnumerator ابتدا در وضعیت شیء Current آن قرار خواهیم داشت و تا زمانیکه متد MoveNext آن فراخوانی نشود هیچ اتفاق دیگری رخ نخواهد داد. هر بار که متد MoveNext این enumerator فرخوانی گردد (برای مثال توسط یک حلقه‌ی foreach) اجرای متد integerList ادامه خواهد یافت تا به yield return بعدی برسیم (سایر اعمال تعریف شده در حالت تعلیق قرار دارند) و همینطور الی آخر.
از همین قابلیت جهت مدیریت اعمال Async پی در پی نیز می‌توان استفاده کرد. State machine فوق تا پایان اولین عملیات تعریف شده صبر می‌کند تا به yield return برسد. سپس با فراخوانی متد MoveNext به عملیات بعدی رهنمون خواهیم شد. به این صورت دیدگاهی پی در پی از یک سلسه عملیات غیرهمزمان حاصل می‌گردد.

خوب ما الان نیاز به یک کلاس داریم که بتواند enumerator ایی از این دست را به صورت خودکار مرحله به مرحله آن هم پس از پایان واقعی عملیات Async قبلی (یا مرحله‌ی قبلی)، اجرا کند. قبل از اختراع چرخ باید متذکر شد که دیگران اینکار را انجام داده‌اند و کتابخانه‌های رایگان و یا سورس بازی برای این منظور موجود است.


ادامه دارد ...

نظرات مطالب
EF Code First #1
سلام مهندس نصیری، چرا این کد توی EF5 خطای کلید خارجی میده؟
کدش از کتاب Code First که معرفی کردین استفاده کردم اما کد خودتون خطا نداره

using System;
using System.Collections.Generic;
namespace ChapterOneProject
{
public class Patient
    {
        public Patient()
        {
            Visits = new List<Visit>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime BirthDate { get; set; }
        //[ForeignKey("AnimalTypeId")]
        
        public AnimalType AnimalType { get; set; }
        //public int AnimalTypeId { get; set; }

        public DateTime FirstVisit { get; set; }
        public List<Visit> Visits { get; set; }
    }

public class Visit
    {
        [Key]
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public String ReasonForVisit { get; set; }
        public String Outcome { get; set; }
        public Decimal Weight { get; set; }

        //[ForeignKey("PatientId")]
        //public virtual Patient Patient { get; set; }
        public int PatientId { get; set; }
    }

public class AnimalType
    {
        public int Id { get; set; }
        public string TypeName { get; set; }
    }
}

کد کانتکست
public class VetContext : DbContext
    {
        public DbSet<Patient> Patients { get; set; }
        public DbSet<Visit> Visits { get; set; }
        //public DbSet<AnimalType> AnimalTypes { get; set; }
    }
و در تابع Main برنامه Console این نوشته شده اما خطا میده و ثبت نمی‌شه
var dog = new AnimalType { TypeName = "Dog" };
            var visit = new List<Visit>
                            {
                                new Visit
                                    {
                                        Date = new DateTime(2011, 9, 1),
                                        Outcome = "Test",
                                        ReasonForVisit = "Test",
                                        Weight = 32,
                                    }
                            };
            var patient = new Patient
                              {
                                  Name = "Sampson",
                                  BirthDate = new DateTime(2008, 1, 28),
                                  AnimalType = dog,
                                  Visits = visit,
                              };
            using (var context = new VetContext())
            {
                context.Patients.Add(patient);
                context.SaveChanges();
            }

کد‌های دیگه تست کردم مشکلی نداشت اما این مورد ؟
با profiler چک کردم خطای عدم توانایی در تبدیل نوع datetime2 به datetime میده
مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
jqGrid یکی از افزونه‌های بسیار محبوب jQuery جهت نمایش جدول مانند اطلاعات، در سمت کلاینت است. توانمندی‌های آن صرفا به نمایش ستون‌ها و ردیف‌ها خلاصه نمی‌شود. قابلیت‌هایی مانند صفحه بندی، مرتب سازی، جستجو، ویرایش توکار، تولید خودکار صفحات افزودن رکوردها، اعتبارسنجی داده‌ها، گروه بندی، نمایش درختی و غیره را نیز به همراه دارد. همچنین به صورت توکار پشتیبانی از راست به چپ را نیز لحاظ کرده‌است.
 مجوز استفاده از فایل‌های جاوا اسکریپتی آن MIT است؛ به این معنا که در هر نوع پروژه‌ای قابل استفاده است. مجوز استفاده از کامپوننت‌های سمت سرور آن که برای نمونه جهت ASP.NET MVC یک سری HTML Helper را تدارک دیده‌اند، تجاری می‌باشد. در ادامه قصد داریم صرفا از فایل‌های JS عمومی آن استفاده کنیم.


دریافت jqGrid

برای دریافت jqGrid می‌توانید به مخزن کد آن، در آدرس https://github.com/tonytomov/jqGrid/releases و یا از طریق NuGet اقدام کنید:
 PM> Install-Package Trirand.jqGrid
استفاده از NuGet بیشتر توصیه می‌شود، زیرا به صورت خودکار وابستگی‌های jQuery و همچنین jQuery UI آن‌را نیز به همراه داشته و نصب خواهد کرد.
از jQuery UI برای تولید صفحات جستجوی بر روی رکوردها و همچنین تولید خودکار صفحات ویرایش و یا افزودن رکوردها استفاده می‌کند. به علاوه آیکن‌ها، قالب و رنگ خود را نیز از jQuery UI دریافت می‌کند. بنابراین اگر قصد تغییر قالب آن‌را داشتید تنها کافی است یک قالب استاندارد دیگر jQuery UI را مورد استفاده قرار دهید.


تنظیمات اولیه فایل Layout سایت

پس از دریافت بسته‌ی نیوگت jqGrid، نیاز است فایل‌های مورد نیاز اصلی آن‌را به شکل زیر به فایل layout پروژه اضافه کرد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    
    <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

    <script src="~/Scripts/jquery-1.7.2.min.js"></script>
    <script src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
    <script src="~/Scripts/i18n/grid.locale-fa.js"></script>
    <script src="~/Scripts/jquery.jqGrid.min.js"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>
فایل jquery.ui.all.css شامل تمامی فایل‌های CSS مرتبط با jQuery UI است و نیازی نیست تا سایر فایل‌های آن‌را لحاظ کرد.
این گرید به همراه فایل زبان فارسی grid.locale-fa.js نیز می‌باشد که در کدهای فوق پیوست شده‌است. البته اگر فرصت کردید نیاز است کمی ترجمه‌های آن بهبود پیدا کنند.


تنظیمات ثانویه site.css

.ui-widget {
}

/*how to move jQuery dialog close (X) button from right to left*/
.ui-jqgrid .ui-jqgrid-caption-rtl {
    text-align: center !important;
}

.ui-dialog .ui-dialog-titlebar-close {
    left: .3em !important;
}

.ui-dialog .ui-dialog-title {
    margin: .1em 0 .1em .8em !important;
    direction: rtl !important;
    float: right !important;
}
احتمالا تنظیمات قلم‌های jQuery UI و یا jqGrid مدنظر شما نیستند و نیاز به تعویض دارند. در اینجا نحوه‌ی بازنویسی آن‌ها را ملاحظه می‌کنید.
همچنین محل قرار گیری دکمه‌ی بسته شدن دیالوگ‌ها و راست به چپ کردن عناوین آن‌ها نیز در اینجا قید شده‌اند.


مدل برنامه

در ادامه قصد داریم لیستی از محصولات را با ساختار ذیل، توسط jqGrid نمایش دهیم:
namespace jqGrid01.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


ساختار داده‌ای مورد نیاز توسط jqGrid

jqGrid مستقل است از فناوری سمت سرور. بنابراین هر چند در عنوان بحث ASP.NET MVC ذکر شده‌است، اما از ASP.NET MVC صرفا جهت بازگرداندن خروجی JSON استفاده خواهیم کرد و این مورد در هر فناوری سمت سرور دیگری نیز می‌تواند انجام شود.
using System.Collections.Generic;

namespace jqGrid01.Models
{
    public class JqGridData
    {
        public int Total { get; set; }

        public int Page { get; set; }

        public int Records { get; set; }

        public IList<JqGridRowData> Rows { get; set; }

        public object UserData { get; set; }
    }

    public class JqGridRowData
    {
        public int Id { set; get; }
        public IList<string> RowCells { set; get; }
    }
}
خروجی JSON مدنظر توسط jqGrid، یک چنین ساختاری را باید داشته باشد.
Total، نمایانگر تعداد صفحات اطلاعات است. عدد Page، شماره صفحه‌ی جاری است. عدد Records، تعداد کل رکوردهای گزارش را مشخص می‌کند. ساختار ردیف‌های آن نیز تشکیل شده‌است از یک Id به همراه سلول‌هایی که باید با فرمت string، بازگشت داده شوند.
UserData اختیاری است. برای مثال اگر خواستید جمع کل صفحه را در ذیل گرید نمایش دهید، می‌توانید یک anonymous object را در اینجا مقدار دهی کنید. خاصیت‌های آن دقیقا باید با نام خاصیت‌های ستون‌های متناظر، یکی باشند. برای مثال اگر می‌خواهید عددی را در ستون Id، در فوتر گرید نمایش دهید، باید نام خاصیت را Id ذکر کنید.


کدهای سمت کلاینت گرید

در اینجا کدهای کامل سمت کلاینت گرید را ملاحظه می‌کنید:
@{
    ViewBag.Title = "Index";
}

<div dir="rtl" align="center">
    <div id="rsperror"></div>
    <table id="list" cellpadding="0" cellspacing="0"></table>
    <div id="pager" style="text-align:center;"></div>
</div>

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#list').jqGrid({
                caption: "آزمایش اول",
                //url from wich data should be requested
                url: '@Url.Action("GetProducts","Home")',
                //type of data
                datatype: 'json',
                jsonReader: { 
                    root: "Rows",
                    page: "Page",
                    total: "Total",
                    records: "Records",
                    repeatitems: true,
                    userdata: "UserData",
                    id: "Id",
                    cell: "RowCells"
                },
                //url access method type
                mtype: 'GET',
                //columns names
                colNames: ['شماره', 'نام محصول', 'موجود است', 'قیمت'],
                //columns model
                colModel: [
                { name: 'Id', index: 'Id', align: 'right', width: 50, sorttype: "number" },
                { name: 'Name', index: 'Name', align: 'right', width: 300 },
                { name: 'IsAvailable', index: 'IsAvailable', align: 'center', width: 100, formatter: 'checkbox' },
                { name: 'Price', index: 'Price', align: 'center', width: 100, sorttype: "number" }
                ],
                //pager for grid
                pager: $('#pager'),
                //number of rows per page
                rowNum: 10,
                rowList: [10, 20, 50, 100],
                //initial sorting column
                sortname: 'Id',
                //initial sorting direction
                sortorder: 'asc',
                //we want to display total records count
                viewrecords: true,
                altRows: true,
                shrinkToFit: true,
                width: 'auto',
                height: 'auto',
                hidegrid: false,
                direction: "rtl",
                gridview: true,
                rownumbers: true,
                footerrow: true,
                userDataOnFooter: true,
                loadComplete: function() {
                    //change alternate rows color
                    $("tr.jqgrow:odd").css("background", "#E0E0E0");
                },
                loadError: function(xhr, st, err) {
                     jQuery("#rsperror").html("Type: " + st + "; Response: " + xhr.status + " " + xhr.statusText);
                }
                //, loadonce: true
            })
            .jqGrid('navGrid', "#pager",
            {
                edit: false, add: false, del: false, search: false,
                refresh: true
            })
            .jqGrid('navButtonAdd', '#pager',
            {
                caption: "تنظیم نمایش ستون‌ها", title: "Reorder Columns",
                onClickButton: function() {
                     jQuery("#list").jqGrid('columnChooser');
                }
            });
        });
    </script>
}
- برای نمایش این گرید، به یک جدول و یک div نیاز است. از جدول با id مساوی list جهت نمایش رکوردهای برنامه استفاده می‌شود. از div با id مساوی pager برای نمایش اطلاعات صفحه بندی و نوار ابزار پایین گرید کمک گرفته خواهد شد.
Div سومی با id مساوی rsperror نیز تعریف شده‌است که از آن جهت نمایش خطاهای بازگشت داده شده از سرور استفاده کرده‌ایم.
- در ادامه نحوه‌ی فراخوانی افزونه‌ی jqGrid را بر روی جدول list ملاحظه می‌کنید.
- خاصیت caption، عنوان نمایش داده شده در بالای گرید را مقدار دهی می‌کند:


- خاصیت url، به آدرسی اشاره می‌کند که قرار است ساختار JqGridData ایی را که پیشتر در مورد آن بحث کردیم، با فرمت JSON بازگشت دهد. در اینجا برای مثال به یک اکشن متد کنترلری در یک پروژه‌ی ASP.NET MVC اشاره می‌کند.
- datatype را برابر json قرار داده‌ایم. از نوع xml نیز پشتیبانی می‌کند.
- شیء jsonReader را از این جهت مقدار دهی کرده‌ایم تا بتوانیم شیء JqGridData را با اصول نامگذاری دات نت، هماهنگ کنیم. برای درک بهتر این موضوع، فایل jquery.jqGrid.src.js را باز کنید و در آن به دنبال تعریف jsonReader بگردید. به یک چنین مقادیر پیش فرضی خواهید رسید:
ts.p.jsonReader = $.extend(true,{
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: true,
cell: "cell",
id: "id",
userdata: "userdata",
subgrid: {root:"rows", repeatitems: true, cell:"cell"}
},ts.p.jsonReader);
برای مثال سلول‌ها را با نام cell دریافت می‌کند که در شیء JqGridData به RowCells تغییر نام یافته‌است. برای اینکه این تغییر نام‌ها توسط jqGrid پردازش شوند، تنها کافی است jsonReader را مطابق تعاریفی که ملاحظه می‌کنید، مقدار دهی کرد.
- در ادامه mtype به GET تنظیم شده‌است. در اینجا مشخص می‌کنیم که عملیات Ajax ایی دریافت اطلاعات از سرور توسط GET انجام شود یا برای مثال توسط POST.
- خاصیت colNames، معرف نام ستون‌های گرید است. برای اینکه این نام‌ها از راست به چپ نمایش داده شوند، باید خاصیت direction به rtl تنظیم شود.
- colModel آرایه‌ای است که تعاریف ستون‌ها را در بر دارد. مقدار name آن باید یک نام منحصربفرد باشد. از این نام در حین جستجو یا ویرایش اطلاعات استفاده می‌شود. مقدار index نامی است که جهت مرتب سازی اطلاعات، به سرور ارسال می‌شود. تنظیم sorttype در اینجا مشخص می‌کند که آیا به صورت پیش فرض، ستون جاری رشته‌ای مرتب شود یا اینکه برای مثال عددی پردازش گردد. مقادیر مجاز آن text (مقدار پیش فرض)، float، number، currency، numeric، int ، integer، date و datetime هستند.
- در ستون IsAvailable، مقدار formatter نیز تنظیم شده‌است. در اینجا توسط formatter، نوع bool دریافتی با یک checkbox نمایش داده خواهد شد.
- خاصیت pager به id متناظری در صفحه اشاره می‌کند.
- توسط rowNum مشخص می‌کنیم که در هر صفحه چه تعداد رکورد باید نمایش داده شوند.
- تعداد رکوردهای نمایش داده شده را می‌توان توسط rowList پویا کرد. در اینجا آرایه‌ای را ملاحظه می‌کنید که توسط اعداد آن، کاربر امکان انتخاب صفحاتی مثلا 100 ردیفه را نیز پیدا می‌کند. rowList به صورت یک dropdown در کنار عناصر راهبری صفحه در فوتر گرید ظاهر می‌شود.
- خاصیت sortname، نحوه‌ی مرتب سازی اولیه گرید را مشخص می‌کند.
- خاصیت sortorder، جهت مرتب سازی اولیه‌ی گردید را تنظیم می‌کند.
- viewrecords: تعداد رکوردها را در نوار ابزار پایین گرید نمایش می‌دهد.
- altRows: سبب می‌شود رنگ متن ردیف‌ها یک در میان متفاوت باشد.
- shrinkToFit: به معنای تنظیم خودکار اندازه‌ی سلول‌ها بر اساس اندازه‌ی داده‌ای است که دریافت می‌کنند.
- width: عرض گرید، که در اینجا به auto تنظیم شده‌است.
- height: طول گرید، که در اینجا به auto جهت محاسبه‌ی خودکار، تنظیم شده‌است.
- gridview: برای بالا بردن سرعت نمایشی به true تنظیم شده‌است. در این حالت کل ردیف یکباره درج می‌شود. اگر از subgird یا حالت نمایش درختی استفاده شود، باید این خاصیت را false کرد.
- rownumbers: ستون سمت راست شماره ردیف‌های خودکار را نمایش می‌دهد.
- footerrow: سبب نمایش ردیف فوتر می‌شود.
- userDataOnFooter: سبب خواهد شد تا خاصیت UserData مقدار دهی شده، در ردیف فوتر ظاهر شود.
- loadComplete : یک callback است که زمان پایان بارگذاری صفحه‌ی جاری را مشخص می‌کند. در اینجا با استفاده از jQuery سبب شده‌ایم تا رنگ پس زمینه‌ی ردیف‌ها یک در میان تغییر کند.
- loadError: اگر از سمت سرور خطایی صادر شود، در این callback قابل دریافت خواهد بود.
- در ادامه توسط فراخوانی متد jqGrid با پارامتر navGrid، در ناحیه pager سبب نمایش دکمه refresh شده‌ایم. این دکمه سبب بارگذاری مجدد اطلاعات گردید از سرور می‌شود.
- همچنین به کمک متد jqGrid با پارامتر navButtonAdd در ناحیه pager، سبب نمایش دکمه‌ای که صفحه‌ی انتخاب ستون‌ها را ظاهر می‌کند، خواهیم شد.



پیشنیاز کدهای سمت سرور jqGrid

اگر به تنظیمات گرید دقت کرده باشید، خاصیت index ستون‌ها، نامی است که به سرور، جهت اطلاع رسانی در مورد فیلتر اطلاعات و مرتب سازی مجدد آن‌ها ارسال می‌گردد. این نام، بر اساس کلیک کاربر بر روی ستون‌های موجود، هر بار می‌توان متفاوت باشد. بنابراین بجای if و else نوشتن‌های طولانی جهت مرتب سازی اطلاعات، می‌توان از کتابخانه‌ی معروفی به نام dynamic LINQ استفاده کرد.
 PM> Install-Package DynamicQuery
به این ترتیب می‌توان قسمت orderby را به صورت پویا و با رشته‌ای دریافتی، مقدار دهی کرد.


کدهای سمت سرور بازگشت اطلاعات به فرمت JSON

در کدهای سمت کلاینت، به اکشن متد GetProducts اشاره شده بود. تعاریف کامل آن‌را در ذیل مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
using jqGrid01.Models;
using jqGrid01.Extensions; // for dynamic OrderBy

namespace jqGrid01.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetProducts(string sidx, string sord, int page, int rows)
        {
            var list = ProductDataSource.LatestProducts;

            var pageIndex = page - 1;
            var pageSize = rows;
            var totalRecords = list.Count;
            var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize);

            var products = list.AsQueryable()
                               .OrderBy(sidx + " " + sord)
                               .Skip(pageIndex * pageSize)
                               .Take(pageSize)
                               .ToList();

            var jqGridData = new JqGridData
            {
                UserData = new // نمایش در فوتر
                {
                    Name = "جمع صفحه",
                    Price = products.Sum(x => x.Price)
                },
                Total = totalPages,
                Page = page,
                Records = totalRecords,
                Rows = (products.Select(product => new JqGridRowData
                                                {
                                                    Id = product.Id,
                                                    RowCells = new List<string>
                                                    {
                                                        product.Id.ToString(CultureInfo.InvariantCulture),
                                                        product.Name,
                                                        product.IsAvailable.ToString(),
                                                        product.Price.ToString(CultureInfo.InvariantCulture)
                                                    }
                                                })).ToList()
            };
            return Json(jqGridData, JsonRequestBehavior.AllowGet);
        }
    }
}
- سطر ProductDataSource.LatestProducts چیزی نیست بجز لیست جنریکی از محصولات.
- امضای متد GetProducts نیز مهم است. دقیقا همین پارامترها با همین نام‌ها از طرف jqGrid به سرور ارسال می‌شوند که توسط آن‌ها ستون مرتب سازی، جهت مرتب سازی، صفحه‌ی جاری و تعداد ردیفی که باید بازگشت داده شوند، قابل دریافت است.
- در این کدها دو قسمت مهم وجود دارند:
الف) متد OrderBy نوشته شده، به صورت پویا عمل می‌کند و از کتابخانه‌ی Dynamic LINQ مایکروسافت بهره می‌برد.
به علاوه توسط Take و Skip کار صفحه بندی و بازگشت تنها بازه‌ای از اطلاعات مورد نیاز، انجام می‌شود.
ب) لیست جنریک محصولات، در نهایت باید با فرمت JqGridData به صورت JSON بازگشت داده شود. نحوه‌ی این Projection را در اینجا می‌توانید ملاحظه کنید.
هر ردیف این لیست، باید تبدیل شود به ردیفی از جنس JqGridRowData، تا توسط jqGrid قابل پردازش گردد.
- توسط مقدار دهی UserData، برچسبی را در ذیل ستون Name و مقداری را در ذیل ستون Price نمایش خواهیم داد.


برای مطالعه‌ی بیشتر

بهترین راهنمای جزئیات این Grid، مستندات آنلاین آن هستند: http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs
همچنین این مستندات را با فرمت PDF نیز می‌توانید مطالعه کنید: http://www.trirand.com/blog/jqgrid/downloads/jqgriddocs.pdf


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid01.zip
 

مثال‌های سری jqGrid تغییرات زیادی داشتند. برای دریافت آن‌ها به این مخزن کد مراجعه کنید.