نظرات مطالب
شروع کار با ASP.NET Web API 2
من یه سوالی برام پیش اومده اینه که
میشه از Web API برای پروژه‌های بزرگ مبتنی بر روی HTTP استفاده کرد؟
منظورم از پروژه‌های بزرگ یعنی Request هایی که شاید اطلاعات برگشتی مثلا بیش از 1000 رکورد باشه
آیا شدنیه؟
یعنی منبع داده بتونه بوسیله Web API عملیات‌های Crud رو بر روی بستر اینترنت برای پروژه‌های این چنینی که امکان واکشی اطلاعات بیشمار و ورود اطلاعات همزمان بوسیله کاربرهای مختلف با دیوایس‌های مختلف وجود داره رو ارائه بده؟
مطالب دوره‌ها
افزونه‌ای برای کپسوله سازی نکات ارسال یک فرم ASP.NET MVC به سرور توسط jQuery Ajax
اگر مطالب سایت جاری را مطالعه و دنبال کرده باشید، تاکنون به صورت پراکنده نکات زیادی را در مورد استفاده از jQuery Ajax تهیه و ارائه کرده‌ایم. در این مطلب قصد داریم تا این نکات را نظم بخشیده و جهت استفاده مجدد، به صورت یک افزونه کپسوله سازی کنیم.

در کدها و افزونه‌ای که در ادامه ارائه خواهند شد، این مسایل درنظر گرفته شده است:

- چگونه اعتبار سنجی سمت کاربر را در حین استفاده از Ajax فعال کنیم.
- چگونه از چندبار کلیک کاربر در حین ارسال فرم به سرور جلوگیری نمائیم.
- چگونه Complex Types قابل تعریف در EF Code first را نیز در اینجا مدیریت کنیم.
- نحوه تعریف صحیح آدرس‌های کنترلرها چگونه باید باشد.
- نحوه اعلام وضعیت لاگین شخص به او، در صورت بروز مشکل.
- ارسال صحیح anti forgery token در حین اعمال Ajax ایی.
- بررسی Ajax بودن درخواست رسیده و تهیه یک فیلتر سفارشی مخصوص آن.
- از کش شدن اطلاعات Ajax ایی جلوگیری شود.


ابتدا معرفی مدل برنامه
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace jQueryMvcSample01.Models
{
    public class User
    {
        [Required(ErrorMessage = "(*)"), DisplayName("نام")]
        public string Name { set; get; }

        public PhoneInfo PhoneInfo { set; get; }
    }

    public class PhoneInfo
    {
        [Required(ErrorMessage = "(*)"), DisplayName("تلفن")]
        public string Phone { get; set; }

        [Required(ErrorMessage = "(*)"), DisplayName("پیش شماره")]
        public string Ext { get; set; }
    }
}
همانطور که ملاحظه می‌کنید، خاصیت PhoneInfo، تو در تو یا به نوعی Complex است. اگر از ابزارهای Scafolding توکار VS.NET برای تولید View متناظر استفاده کنیم، فیلد تو در توی PhoneInfo را لحاظ نخواهد کرد، اما ... مهم نیست. تعریف دستی آن هم کار می‌کند.


کدهای کنترلر برنامه

using System.Web.Mvc;
using jQueryMvcSample01.Models;
using jQueryMvcSample01.Security;

namespace jQueryMvcSample01.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش فرم
        }

        [HttpPost]
        [AjaxOnly] //فقط در حالت ای‌جکس قابل دسترسی باشد
        [ValidateAntiForgeryToken]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                // ذخیره سازی در بانک اطلاعاتی ...
                System.Threading.Thread.Sleep(3000);

                return Content("ok");//اعلام موفقیت آمیز بودن کار
            }

            return Content(null);//ارسال خطا
        }
    }
}
در اینجا در متد Index، اطلاعات شیء User به صورت Ajaxایی دریافت شده و پس از آن برای مثال قابلیت ذخیره سازی را خواهد داشت.
چند نکته در اینجا حائز اهمیت هستند:
الف) استفاده از ویژگی AjaxOnly (که کدهای آن‌را در پروژه پیوست می‌توانید مشاهده نمائید)، جهت صرفا پردازش درخواست‌های Ajaxایی.
ب) استفاده از ویژگی ValidateAntiForgeryToken در حین اعمال اجکسی. اگر سایت‌های مختلف را در اینباره جستجو کنید، عموما برای پردازش آن در حین استفاده از jQuery Ajax بسیار مشکل دارند.
ج) استفاده از return Content برای اعلام نتیجه کار. اگر اطلاعات ثبت شد، یک ok یا هر عبارت دیگری که علاقمند بودید ارسال گردیده و در غیراینصورت null بازگشت داده می‌شود.


کدهای افزونه PostMvcFormAjax

// <![CDATA[
(function ($) {
    $.fn.PostMvcFormAjax = function (options) {
        var defaults = {
            postUrl: '/',
            loginUrl: '/login',
            beforePostHandler: null,
            completeHandler: null,
            errorHandler: null
        };
        var options = $.extend(defaults, options);

        var validateForm = function (form) {
            //فعال سازی دستی اعتبار سنجی جی‌کوئری
            var val = form.validate();
            val.form();
            return val.valid();
        };

        return this.each(function () {
            var form = $(this);
            //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود
            if (!validateForm(form)) return;

            //در اینجا می‌توان مثلا دکمه‌ای را غیرفعال کرد
            if (options.beforePostHandler)
                options.beforePostHandler(this);

            //اطلاعات نباید کش شوند
            $.ajaxSetup({ cache: false });

            $.ajax({
                type: "POST",
                url: options.postUrl,
                data: form.serialize(), //تمام فیلدهای فرم منجمله آنتی فرجری توکن آن‌را ارسال می‌کند
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا می‌شود
                    }
                    else if (status === 'error' || !data) {
                        if (options.errorHandler)
                            options.errorHandler(this);
                    }
                    else {
                        if (options.completeHandler)
                            options.completeHandler(this);
                    }
                }

            });
        });
    };
})(jQuery);
// ]]>
چند نکته مهم در تهیه این افزونه رعایت شده:
الف) فعال سازی دستی اعتبار سنجی جی‌کوئری، از این جهت که این نوع اعتبار سنجی به صورت پیش فرض تنها در حالت postback و ارسال کامل صفحه به سرور فعال می‌شود.
ب) استفاده از متد serialize جهت پردازش یکباره کل اطلاعات و فیلدهای یک فرم.
نکته مهم این متد ارسال فیلد مخفی anti forgery token نیز می‌باشد. فقط باید دقت داشت که این فیلد در حالتی که dataType به json تنظیم شود و همچنین از متد serialize استفاده گردد، در ASP.NET MVC پردازش نمی‌گردد (خیلی مهم!). به همین جهت در اینجا dataType تنظیمات jQuery Ajax حذف شده است.
ج) تنظیم cache به false در تنظیمات ابتدایی jQuery Ajax تا اطلاعات ارسالی و دریافتی کش نشوند و مشکل ساز نگردند.
د) بررسی xhr.status == 403 که توسط SiteAuthorizeAttribute (جایگزین بهتر فیلتر Authorize توکار ASP.NET MVC که کدهای آن در پروژه پیوست قابل دریافت است) و هدایت کاربر به صفحه لاگین


تعریف View ایی که از اشیاء تو در تو استفاده می‌کند و همچنین از افزونه فوق برای ارسال اطلاعات بهره خواهد برد:

@model jQueryMvcSample01.Models.User
@{
    ViewBag.Title = "تعریف کاربر";
    var postUrl = Url.Action(actionName: "Index", controllerName: "Home");
}
@using (Html.BeginForm(actionName: "Index", controllerName: "Home",
                       method: FormMethod.Post,
                       htmlAttributes: new { id = "UserForm" }))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()

    <fieldset>
        <legend>تعریف کاربر</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Ext)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Phone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Phone)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Phone)
        </div>
        <p>
            <input type="submit" id="btnSave" value="ارسال" />
        </p>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#btnSave").click(function (event) {
                //جلوگیری از پست بک به سرور
                event.preventDefault();

                var button = $(this);

                $("#UserForm").PostMvcFormAjax({
                    postUrl: '@postUrl',
                    loginUrl: '/login',
                    beforePostHandler: function () {
                        //غیرفعال سازی دکمه ارسال
                        button.attr('disabled', 'disabled');
                        button.val("...");
                    },
                    completeHandler: function () {
                        //فعال سازی مجدد دکمه ارسال
                        alert('انجام شد');
                        button.removeAttr('disabled');
                        button.val("ارسال");
                    },
                    errorHandler: function () {
                        alert('خطایی رخ داده است');
                    }
                });
            });
        });
    </script>
}
همانطور که عنوان شد، مهم نیست که اشیاء تو در تو توسط ابزار Scafolding پشتیبانی نمی‌شود. این نوع خواص را به همان نحو متداول ذکر زنجیره وار خواص می‌توان معرفی و استفاده کرد:
 @Html.EditorFor(model => model.PhoneInfo.Phone)
هم اعتبار سنجی سمت کلاینت آن کار می‌کند و هم اطلاعات آن به اشیاء و خواص متناظر به خوبی نگاشت خواهد شد:


در ادامه نحوه استفاده از افزونه PostMvcFormAjax را مشاهده می‌کنید. چند نکته نیز در اینجا حائز اهمیت هستند:
الف) توسط htmlAttributes یک id برای فرم تعریف کرده‌ایم تا در افزونه PostMvcFormAjax مورد استفاده قرار گیرد.
ب) postUrl و loginUrl را همانند متغیر تعریف شده در ابتدای View توسط Url.Action باید تعریف کرد تا در صورتیکه سایت ما در ریشه اصلی قرار نداشت، باز هم به صورت خودکار مسیر صحیحی محاسبه و ارائه گردد.
ج) نحوه غیرفعال سازی و فعال سازی دکمه submit را در روال‌های beforePostHandler و completeHandler ملاحظه می‌کنید. این مساله برای جلوگیری از کلیک‌های مجدد یک کاربر ناشکیبا و جلوگیری از ثبت اطلاعات تکراری بسیار مهم است.
د) کل این اطلاعات، در یک section به نام JavaScript ثبت شده است. این section در فایل layout برنامه به صورت زیر مورد استفاده قرار خواهد گرفت و به این ترتیب مقدار دهی خواهد شد:
<head>
    <title>@ViewBag.Title</title>    
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.PostMvcFormAjax.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>

دریافت کدهای کامل این قسمت
jQueryMvcSample01.zip

 
مطالب
خلاصه‌ای کوتاه در مورد WinRT

WinRT چیست؟

مایکروسافت جهت سهولت تولید برنامه‌های جدید Metro-style ، لمسی (touch-centric) و tablets ویندوز 8 ، اقدام به بازنویسی مجدد Windows-API کرده و نام آن‌را WinRT گذاشته است. بنابراین همانند آنچه که در مورد API‌ ویندوز از روز اول پیدایش آن مرسوم بوده، این API جدید، از نوع native (نوشته شده با CPP) می‌باشد و با کمک دات نت فریم ورک تهیه نشده است. این API جدید مبتنی بر فناوری قدیمی COM است، بنابراین مطابق معمول توسط هر نوع برنامه‌ و سیستمی در ویندوز قابل دسترسی است. تفاوتی نمی‌کند که CPP یا دلفی باشد یا دات نت. به صورت خلاصه ویندوز 8 دو طراحی جدید (WinRT) و قدیم (Win32 API) را با هم پشتیبانی می‌کند. اگر آن‌را صحیح‌تر بخواهیم معرفی کنیم، WinRT درحقیقت محصور کننده‌ی (Wrapper) همان Win32 API سابق است (در پشت صحنه همان dll های سابق ویندوز را بارگذاری و استفاده می‌کند) جهت تطابق با نیازهای دهه اخیر و سال‌های پیش رو.


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

اینبار WinRT برخلاف Win32 API (که در زمان ارائه آن اصلا دات نتی در کار نبود)، جهت سازگاری با دات نت طراحی شده است. این طراحی جدید ILDasm metadata را در اختیار برنامه نویس‌های دات نت قرار می‌دهد و به این ترتیب IntelliSense و قابلیت‌های Debugging ویژوال استودیو همانند کدهای مدیریت شده‌ی دات نت جهت برنامه نویسی مبتنی بر WinRT در اختیار برنامه نویس‌ها خواهد بود (فرمت ارائه شده، ECMA 335 metadata format می‌باشد). همچنین اشیاء COM متعلق به WinRT به خوبی توسط GC (آشغال جمع کن) دات نت جهت مدیریت بهتر حافظه، تحت نظر می‌باشند.
بنابراین از دیدگاه یک برنامه نویس دات نت، کل WinRT به صورت managed assemblies مشاهده می‌شود، اینترفیس‌های آن همان اینترفیس‌های دات نتی خواهند بود و کلاس‌های آن نیز به همین ترتیب. این مشکلی بود/هست که با Win32 API در دات نت وجود دارد و دسترسی به آن به این سادگی و یکپارچگی میسر نیست (هر چند تا الان کل اینترفیس آن جهت استفاده در دات نت نیز ترجمه شده است). در اینجا شما ارجاعاتی را به فایل‌هایی با پسوند winmd یا windows metadata، به پروژه‌ی دات نتی خود اضافه می‌کنید و سپس CLR قادر خواهد بود تا کلیه اطلاعات لازم جهت کار با WinRT را از آن‌ها استخراج کند (این فایل‌ها در پوشه C:\Program Files (x86)\Windows Kits\8.0\Windows Metadata و C:\Windows\system32\winmetadat ویندوز 8 قابل مشاهده و دسترسی هستند).


تفاوت‌های مهم امکانات نمایشی WinRT با Win32 API کدامند؟

تفاوت مهم WinRT با Win32 API از دیدگاه برنامه نویس‌ها، امکان دسترسی بیشتر به آن از طریق زبان‌های مختلف می‌باشد. WinRT همانند Win32 API توسط CPP ، دات نت و سایر روش‌های مرسوم دیگر قابل دسترسی و توسعه است. اما اینبار WinRT برخلاف Win32 API ، از طریق HTML و جاوا اسکریپت هم قابل توسعه است. در این حالت کدهای شما توسط Chakra JavaScript engine که از اینترنت اکسپلورر 9 به بعد ارائه شده، اجرا خواهد شد. بنابراین «برفراز» WinRT دو لایه نمایشی (presentation layer) قابل طراحی و دسترسی است. XAML و زبان‌های متداول برنامه نویسی موجود مانند سی شارپ و وی بی دات نت و غیره. همچنین HTML/CSS هم مجال ابراز وجود یافته است. البته XAML تنها لایه نمایشی کلیه زبان‌های قدیمی موجود مانند سی شارپ، وی بی دات نت، CPP و غیره خواهد بود. به همین جهت Expression Blend جدید نیز از HTML 5 پشتیبانی می‌کند.
همچنین در WinRT ، قسمت‌های GDI و Message loop متداول Win32 API حذف شده است و از DirectX استفاده می‌کند. برای نمونه کدهای XAML شما توسط DirectX رندر می‌شود. البته این مطلب جدیدی نیست و از زمان ارائه WPF شاهد این مساله بوده‌ایم.


وضعیت توسعه پذیری WinRT  چگونه است؟

علاوه بر این‌ها، برخلاف Win32 API ، اینبار WinRT قابل توسعه است و Extensions SDK برای آن ارائه شده است.


آیا WinRT شاهد تغییرات امنیتی خاصی هم بوده است؟

نکته‌ی مهمی که در طراحی WinRT به آن توجه شده است، امنیت می‌باشد. برنامه‌های WinRT شبیه به برنامه‌های سیلورلایت در یک Sandbox اجرا می‌شوند. به این معنا که جهت ذخیره سازی اطلاعات خود از یک isolated storage استفاده می‌کنند. برای کار با file system نیاز به تائید کاربر دارند و خلاصه دیگر به سادگی نمی‌توان از مرزهای این نوع برنامه‌ها رد شد و سیستم عاملی را root کرد. برای نمونه برنامه نویس‌های دات نت دسترسی به فضای نام System.IO.FileStream را دیگر نخواهند داشت و تنها قسمتی از دات نت «برفراز» WinRT و مدل امنیتی جدید آن معنا پیدا می‌کند. همچنین برفراز این API جدید، تولید مثلا Device drivers هم دیگر معنا پیدا نمی‌کند. این محدودیت‌های امنیتی برای برنامه‌ نویس‌های native هم وجود دارد و تفاوتی نمی‌کند. کلا برنامه‌های جدید Metro-style در یک قرنطینه‌ی کامل امنیتی اجرا می‌شوند. برای مثال اگر برنامه‌ای نیاز به دسترسی به یک WebCam را داشته باشد، همانند برنامه‌های سیلورلایت ابتدا باید کاربر تائید کرده و سپس برنامه مجوز امنیتی کار با مثلا یک WebCam را خواهد یافت. همچنین تمام برنامه‌های جدید Metro-style باید جهت ارائه در فروشگاه جدید ویندوز 8، دارای امضای دیجیتال معتبر نیز باشند.


آیا جهت توسعه‌ی برنامه‌های چندریسمانی و غیرهمزمان تمهیدات خاصی در WinRT پیش‌بینی شده است؟

در طراحی جدید WinRT به اعمال asynchronous به شدت توجه شده است. هر عملی که بیش از 50 میلی ثانیه طول بکشد به صورت خودکار تبدیل به یک عمل asynchronous خواهد شد تا برنامه‌ها مرتبا در حین اجرای اعمال زمانبر هنگ نکرده و ترد اصلی برنامه را بلاک نکنند. علاوه بر این‌ها WinRT از طریق IAsyncOperation interface خود، امکان استفاده از واژه‌های جدید کلیدی async/await سی شارپ 5 را نیز مهیا می‌سازد.


آیا WinRT آمده است تا جایگزینی برای دات نت و سیلورلایت و امثال آن باشد؟

خیر. WinRT نگارش دوم Win32 API است با هدف توسعه پذیری، استفاده از دایرکت ایکس و فناوری‌های جدید که عموما از شتاب دهنده‌های سخت افزاری هم بهره‌مند هستند بجای GDI سابق، استفاده ساده‌تر در زبان‌های دیگر به کمک فایل‌های استاندارد Windows Meta data آن می‌باشد. همچنین این API جدید دسترسی به امکانات ویندوز را هم توسط HTML و جاوا اسکریپت، علاوه بر امکانات مهیای سابق میسر ساخته است. هم اکنون نگارش 4 و نیم دات نت در ویندوز 8 ارائه شده است و توسط هر دو سیستم سابق و جدید قابل استفاده می‌باشد. البته باید در نظر داشت که جهت استفاده از WinRT به دلایل محدودیت‌های امنیتی اعمال شده به آن و همچنین استفاده از XAML به تنها عنوان لایه نمایشی سیستم‌های متداول غیر HTML ایی، دات نت فریم ورک به امکانات و کلاس‌های کمتری نسبت به حالت متداول کار با آن، دسترسی دارد (جهت درک بهتر این محدودیت‌ها می‌توان به طراحی سیلورلایت مراجعه کرد). این را هم باید اضافه کرد که ویندوز 8 توانایی اجرای هر دو نوع برنامه‌های سبک جدید مترو و متداول دسکتاپ قدیمی را دارا است.


جهت آشنایی بیشتر با WinRT می‌توان به مجموعه‌ای از ویدیوهای مرتبط آن مراجعه کرد:
http://channel9.msdn.com/Events/BUILD/BUILD2011?t=windows%2Bruntime


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



مدل برنامه

در اینجا قصد داریم لیستی را با ساختار کلاس Product در اختیار Kendo UI گرید قرار دهیم:
namespace KendoUI03.Models
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
        public bool IsAvailable { set; get; }
    }
}


پیشنیاز تامین داده مخصوص Kendo UI Grid

برای ارائه اطلاعات مخصوص Kendo UI Grid، ابتدا باید درنظر داشت که این گرید، درخواست‌های صفحه بندی خود را با فرمت ذیل ارسال می‌کند. همانطور که مشاهده می‌کنید، صرفا یک کوئری استرینگ با فرمت JSON را دریافت خواهیم کرد:
 /api/products?{"take":10,"skip":0,"page":1,"pageSize":10,"sort":[{"field":"Id","dir":"desc"}]}
سپس این گرید نیاز به سه فیلد، در خروجی JSON نهایی خواهد داشت:
{
"Data":
[
{"Id":1500,"Name":"نام 1500","Price":2499.0,"IsAvailable":false},
{"Id":1499,"Name":"نام 1499","Price":2498.0,"IsAvailable":true}
],
"Total":1500,
"Aggregates":null
}
فیلد Data که رکوردهای گرید را تامین می‌کنند. فیلد Total که بیانگر تعداد کل رکوردها است و Aggregates که برای گروه بندی بکار می‌رود.

می‌توان برای تمام این‌ها، کلاس و Parser تهیه کرد و یا ... پروژه‌ی سورس بازی به نام  Kendo.DynamicLinq نیز چنین کاری را میسر می‌سازد که در ادامه از آن استفاده خواهیم کرد. برای نصب آن تنها کافی است دستور ذیل را صادر کنید:
 PM> Install-Package Kendo.DynamicLinq
Kendo.DynamicLinq به صورت خودکار System.Linq.Dynamic را نیز نصب می‌کند که از آن جهت صفحه بندی پویا استفاده خواهد شد.


تامین کننده‌ی داده سمت سرور

همانند مطلب کار با Kendo UI DataSource ، یک ASP.NET Web API Controller جدید را به پروژه اضافه کنید و همچنین مسیریابی‌های مخصوص آن‌را به فایل global.asax.cs نیز اضافه نمائید.
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Kendo.DynamicLinq;
using KendoUI03.Models;
using Newtonsoft.Json;

namespace KendoUI03.Controllers
{
    public class ProductsController : ApiController
    {
        public DataSourceResult Get(HttpRequestMessage requestMessage)
        {
            var request = JsonConvert.DeserializeObject<DataSourceRequest>(
                requestMessage.RequestUri.ParseQueryString().GetKey(0)
            );

            var list = ProductDataSource.LatestProducts;
            return list.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort, request.Filter);
        }
    }
}
تمام کدهای این کنترلر همین چند سطر فوق هستند. با توجه به ساختار کوئری استرینگی که در ابتدای بحث عنوان شد، نیاز است آن‌را توسط کتابخانه‌ی JSON.NET تبدیل به یک نمونه از DataSourceRequest نمائیم. این کلاس در Kendo.DynamicLinq تعریف شده‌است و حاوی اطلاعاتی مانند take و skip کوئری LINQ نهایی است.
ProductDataSource.LatestProducts صرفا یک لیست جنریک تهیه شده از کلاس Product است. در نهایت با استفاده از متد الحاقی جدید ToDataSourceResult، به صورت خودکار مباحث صفحه بندی سمت سرور به همراه مرتب سازی اطلاعات، صورت گرفته و اطلاعات نهایی با فرمت DataSourceResult بازگشت داده می‌شود. DataSourceResult نیز در Kendo.DynamicLinq تعریف شده و سه فیلد یاد شده‌ی Data، Total و Aggregates را تولید می‌کند.

تا اینجا کارهای سمت سرور این مثال به پایان می‌رسد.


تهیه View نمایش اطلاعات ارسالی از سمت سرور

اعمال مباحث بومی سازی
<head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Language" content="fa" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    <title>Kendo UI: Implemeting the Grid</title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <!--شیوه نامه‌ی مخصوص راست به چپ سازی-->
    <link href="styles/kendo.rtl.min.css" rel="stylesheet" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>

    <!--محل سفارشی سازی پیام‌ها و مسایل بومی-->
    <script src="js/cultures/kendo.culture.fa-IR.js" type="text/javascript"></script>
    <script src="js/cultures/kendo.culture.fa.js" type="text/javascript"></script>
    <script src="js/messages/kendo.messages.en-US.js" type="text/javascript"></script>

    <style type="text/css">
        body {
            font-family: tahoma;
            font-size: 9pt;
        }
    </style>

    <script type="text/javascript">
        // جهت استفاده از فایل: kendo.culture.fa-IR.js
        kendo.culture("fa-IR");
    </script>
</head>
- در اینجا چند فایل js و css جدید اضافه شده‌اند. فایل kendo.rtl.min.css جهت تامین مباحث RTL توکار Kendo UI کاربرد دارد.
- سپس سه فایل kendo.culture.fa-IR.js، kendo.culture.fa.js و kendo.messages.en-US.js نیز اضافه شده‌اند. فایل‌های fa و fa-Ir آن هر چند به ظاهر برای ایران طراحی شده‌اند، اما نام ماه‌های موجود در آن عربی است که نیاز به ویرایش دارد. به همین جهت به سورس این فایل‌ها، جهت ویرایش نهایی نیاز خواهد بود که در پوشه‌ی src\js\cultures مجموعه‌ی اصلی Kendo UI موجود هستند (ر.ک. فایل پیوست).
- فایل kendo.messages.en-US.js حاوی تمام پیام‌های مرتبط با Kendo UI است. برای مثال«رکوردهای 10 تا 15 از 1000 ردیف» را در اینجا می‌توانید به فارسی ترجمه کنید.
- متد kendo.culture کار مشخص سازی فرهنگ بومی برنامه را به عهده دارد. برای مثال در اینجا به fa-IR تنظیم شده‌است. این مورد سبب خواهد شد تا از فایل kendo.culture.fa-IR.js استفاده گردد. اگر مقدار آن‌را به fa تنظیم کنید، از فایل kendo.culture.fa.js کمک گرفته خواهد شد.

راست به چپ سازی گرید
تنها کاری که برای راست به چپ سازی Kendo UI Grid باید صورت گیرد، محصور سازی div آن در یک div با کلاس مساوی k-rtl است:
    <div class="k-rtl">
        <div id="report-grid"></div>
    </div>
k-rtl و تنظیمات آن در فایل kendo.rtl.min.css قرار دارند که در ابتدای head صفحه تعریف شده‌است.

تامین داده و نمایش گرید

در ادامه کدهای کامل DataSource و Kendo UI Grid را ملاحظه می‌کنید:
    <script type="text/javascript">
        $(function () {
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    },
                    parameterMap: function (options) {
                        return kendo.stringify(options);
                    }
                },
                schema: {
                    data: "Data",
                    total: "Total",
                    model: {
                        fields: {
                            "Id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است
                            "Name": { type: "string" },
                            "IsAvailable": { type: "boolean" },
                            "Price": { type: "number" }
                        }
                    }
                },
                error: function (e) {
                    alert(e.errorThrown);
                },
                pageSize: 10,
                sort: { field: "Id", dir: "desc" },
                serverPaging: true,
                serverFiltering: true,
                serverSorting: true
            });

            $("#report-grid").kendoGrid({
                dataSource: productsDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                filterable: true,
                reorderable: true,
                columnMenu: true,
                columns: [
                    { field: "Id", title: "شماره", width: "130px" },
                    { field: "Name", title: "نام محصول" },
                    {
                        field: "IsAvailable", title: "موجود است",
                        template: '<input type="checkbox" #= IsAvailable ? checked="checked" : "" # disabled="disabled" ></input>'
                    },
                    { field: "Price", title: "قیمت", format: "{0:c}" }
                ]
            });
        });
    </script>
- با تعاریف مقدماتی Kendo UI DataSource پیشتر آشنا شده‌ایم و قسمت read آن جهت دریافت اطلاعات از سمت سرور کاربرد دارد.
- در اینجا ذکر contentType الزامی است. زیرا ASP.NET Web API بر این اساس است که تصمیم می‌گیرد، خروجی را به صورت JSON ارائه دهد یا XML.
- با استفاده از parameterMap، سبب خواهیم شد تا پارامترهای ارسالی به سرور، با فرمت صحیحی تبدیل به JSON شده و بدون مشکل به سرور ارسال گردند.
- در قسمت schema باید نام فیلدهای موجود در DataSourceResult دقیقا مشخص شوند تا گرید بداند که data را باید از چه فیلدی استخراج کند و تعداد کل ردیف‌ها در کدام فیلد قرار گرفته‌است.
- نحوه‌ی تعریف model را نیز در اینجا ملاحظه می‌کنید. ذکر نوع فیلدها در اینجا بسیار مهم است و اگر قید نشوند، در حین جستجوی پویا به مشکل برخواهیم خورد. زیرا پیش فرض نوع تمام فیلدها string است و در این حالت نمی‌توان عدد 1 رشته‌ای را با یک فیلد از نوع int در سمت سرور مقایسه کرد.
- در اینجا serverPaging، serverFiltering و serverSorting نیز به true تنظیم شده‌اند. اگر این مقدار دهی‌ها صورت نگیرد، این اعمال در سمت کلاینت انجام خواهند شد.

پس از تعریف DataSource، تنها کافی است آن‌را به خاصیت dataSource یک kendoGrid نسبت دهیم.
- autoBind: true سبب می‌شود تا اطلاعات DataSource بدون نیاز به فراخوانی متد read آن به صورت خودکار دریافت شوند.
- با تنظیم scrollable: false، اعلام می‌کنیم که قرار است تمام رکوردها در معرض دید قرارگیرند و اسکرول پیدا نکنند.
- pageable: true صفحه بندی را فعال می‌کند. این مورد نیاز به تنظیم pageSize: 10 در قسمت DataSource نیز دارد.
- با sortable: true مرتب سازی ستون‌ها با کلیک بر روی سرستون‌ها فعال می‌گردد.
- filterable: true به معنای فعال شدن جستجوی خودکار بر روی فیلدها است. کتابخانه‌ی Kendo.DynamicLinq حاصل آن‌را در سمت سرور مدیریت می‌کند.
- reorderable: true سبب می‌شود تا کاربر بتواند محل قرارگیری ستون‌ها را تغییر دهد.
- ذکر columnMenu: true اختیاری است. اگر ذکر شود، امکان مخفی سازی انتخابی ستون‌ها نیز مسیر خواهد شد.
- در آخر ستون‌های گرید مشخص شده‌اند. با تعیین "{format: "{0:c سبب نمایش فیلدهای قیمت با سه رقم جدا کننده خواهیم شد. مقدار ریال آن از فایل فرهنگ جاری تنظیم شده دریافت می‌گردد. با استفاده از template تعریف شده نیز سبب نمایش فیلد bool به صورت یک checkbox خواهیم شد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI03.zip
مطالب
کار با Kendo UI DataSource
Kendo UI DataSource جهت تامین داده‌های سمت کلاینت ویجت‌های مختلف KendoUI طراحی شده‌است و به عنوان یک اینترفیس استاندارد قابل استفاده توسط تمام کنترل‌های داده‌ای Kendo UI کاربرد دارد. Kendo UI DataSource امکان کار با منابع داده محلی، مانند اشیاء و آرایه‌های جاوا اسکریپتی و همچنین منابع تامین شده از راه دور، مانند JSON، JSONP و XML را دارد. به علاوه توسط آن می‌توان اعمال ثبت، ویرایش و حذف اطلاعات، به همراه صفحه بندی، گروه بندی و مرتب سازی داده‌ها را کنترل کرد.


استفاده از منابع داده محلی
در ادامه مثالی را از نحوه‌ی استفاده از یک منبع داده محلی جاوا اسکریپتی، مشاهده می‌کنید:
    <script type="text/javascript">
        $(function () {
            var cars = [
                { "Year": 2000, "Make": "Hyundai", "Model": "Elantra" },
                { "Year": 2001, "Make": "Hyundai", "Model": "Sonata" },
                { "Year": 2002, "Make": "Toyota", "Model": "Corolla" },
                { "Year": 2003, "Make": "Toyota", "Model": "Yaris" },
                { "Year": 2004, "Make": "Honda", "Model": "CRV" },
                { "Year": 2005, "Make": "Honda", "Model": "Accord" },
                { "Year": 2000, "Make": "Honda", "Model": "Accord" },
                { "Year": 2002, "Make": "Kia", "Model": "Sedona" },
                { "Year": 2004, "Make": "Fiat", "Model": "One" },
                { "Year": 2005, "Make": "BMW", "Model": "M3" },
                { "Year": 2008, "Make": "BMW", "Model": "X5" }
            ];

            var carsDataSource = new kendo.data.DataSource({
                data: cars
            });

            carsDataSource.read();
            alert(carsDataSource.total());
        });
    </script>
در اینجا cars آرایه‌ای از اشیاء جاوا اسکریپتی بیانگر ساختار یک خودرو است. سپس برای معرفی آن به Kendo UI، کار با مقدار دهی خاصیت data مربوط به new kendo.data.DataSource شروع می‌شود.
ذکر new kendo.data.DataSource به تنهایی به معنای مقدار دهی اولیه است و در این حالت منبع داده مورد نظر، استفاده نخواهد شد. برای مثال اگر متد total آن‌را جهت یافتن تعداد عناصر موجود در آن فراخوانی کنید، صفر را بازگشت می‌دهد. برای شروع به کار با آن، نیاز است ابتدا متد read را بر روی این منبع داده مقدار دهی شده، فراخوانی کرد.


استفاده از منابع داده راه دور

در برنامه‌های کاربردی، عموما نیاز است تا منبع داده را از یک وب سرور تامین کرد. در اینجا نحوه‌ی خواندن اطلاعات JSON بازگشت داده شده از جستجوی توئیتر را مشاهده می‌کنید:
    <script type="text/javascript">
        $(function () {
            var twitterDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "http://search.twitter.com/search.json",
                        dataType: "jsonp",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET',
                        data: { q: "#kendoui" }
                    },
                    schema: { data: "results" }
                },
                error: function (e) {
                    alert(e.errorThrown.stack);
                }
            });
        });
    </script>
در قسمت transport، جزئیات تبادل اطلاعات با سرور راه دور مشخص می‌شود؛ برای مثال url ارائه دهنده‌ی سرویس، dataType بیانگر نوع داده مورد انتظار و data کار مقدار دهی پارامتر مورد انتظار توسط سرویس توئیتر را انجام می‌دهد. در اینجا چون صرفا عملیات خواندن اطلاعات صورت می‌گیرد، خاصیت read مقدار دهی شده‌است.
در قسمت schema مشخص می‌کنیم که اطلاعات JSON بازگشت داده شده توسط توئیتر، در فیلد results آن قرار دارد.


کار با منابع داده OData

علاوه بر فرمت‌های یاد شده، Kendo UI DataSource امکان کار با اطلاعاتی از نوع OData را نیز دارا است که تنظیمات ابتدایی آن به صورت ذیل است:
    <script type="text/javascript">
            var moviesDataSource = new kendo.data.DataSource({
                type: "odata",
                transport: {
                    read: "http://demos.kendoui.com/service/Northwind.svc/Orders"
                },
                error: function (e) {
                    alert(e.errorThrown.stack);
                }
            });
        });
    </script>
همانطور که ملاحظه می‌کنید، تنظیمات ابتدایی آن اندکی با حالت remote data پیشین متفاوت است. در اینجا ابتدا نوع داده‌ی بازگشتی مشخص می‌شود و در قسمت transport، خاصیت read آن، آدرس سرویس را دریافت می‌کند.


یک مثال: دریافت اطلاعات از ASP.NET Web API

یک پروژه‌ی جدید ASP.NET را آغاز کنید. تفاوتی نمی‌کند که Web forms باشد یا MVC؛ از این جهت که مباحث Web API در هر دو یکسان است.
سپس یک کنترلر جدید Web API را به نام ProductsController با محتوای زیر ایجاد کنید:
using System.Collections.Generic;
using System.Web.Http;

namespace KendoUI02
{
    public class Product
    {
        public int Id { set; get; }
        public string Name { set; get; }
    }

    public class ProductsController : ApiController
    {
        public IEnumerable<Product> Get()
        {
            var products = new List<Product>();
            for (var i = 1; i <= 100; i++)
            {
                products.Add(new Product { Id = i, Name = "Product " + i });
            }
            return products;
        }
    }
}
در این مثال، هدف صرفا ارائه یک خروجی ساده JSON از طرف سرور است.
در ادامه نیاز است تعریف مسیریابی ذیل نیز به فایل Global.asax.cs برنامه اضافه شود تا بتوان به آدرس api/products در سایت، دسترسی یافت:
using System;
using System.Web.Http;
using System.Web.Routing;

namespace KendoUI02
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "api/{controller}/{id}",
               defaults: new { id = RouteParameter.Optional }
               );
        }
    }
}

در ادامه فایلی را به نام Index.html (یا در یک View و یا یک فایل aspx دلخواه)، محتوای ذیل را اضافه کنید:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Kendo UI: Implemeting the Grid</title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>
</head>
<body>

    <div id="report-grid"></div>
    <script type="text/javascript">
        $(function () {
            var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    alert(e.errorThrown.stack);
                },
                pageSize: 5,
                sort: { field: "Id", dir: "desc" }
            });

            $("#report-grid").kendoGrid({
                dataSource: productsDataSource,
                autoBind: true,
                scrollable: false,
                pageable: true,
                sortable: true,
                columns: [
                    { field: "Id", title: "#" },
                    { field: "Name", title: "Product" }
                ]
            });
        });
    </script>
</body>
</html>
- ابتدا فایل‌های اسکریپت و CSS مورد نیاز Kendo UI اضافه شده‌اند.
- گرید صفحه، در محل div ایی با id مساوی report-grid تشکیل خواهد شد.
- سپس DataSource ایی که به آدرس api/products اشاره می‌کند، تعریف شده و در آخر productsDataSource را توسط یک kendoGrid نمایش داده‌ایم.
- نحوه‌ی تعریف productsDataSource، در قسمت استفاده از منابع داده راه دور ابتدای بحث توضیح داده شد. در اینجا فقط دو خاصیت pageSize و sort نیز به آن اضافه شده‌اند. این دو خاصیت بر روی نحوه‌ی نمایش گرید نهایی تاثیر گذار هستند. pageSize تعداد رکورد هر صفحه را مشخص می‌کند و sort نحوه‌ی مرتب سازی را بر اساس فیلد Id و در حالت نزولی قرار می‌دهد.
- در ادامه، ابتدایی‌ترین حالت کار با kendoGrid را ملاحظه می‌کنید.
- تنظیم dataSource و autoBind: true (حالت پیش فرض)، سبب خواهند شد تا به صورت خودکار، اطلاعات JSON از مسیر api/products خوانده شوند.
- سه خاصیت بعدی صفحه بندی و مرتب سازی خودکار ستون‌ها را فعال می‌کنند.
- در آخر هم دو ستون گرید، بر اساس نام‌های خواص کلاس Product تعریف شده‌اند.


سورس کامل این قسمت را از اینجا می‌توانید دریافت کنید:
KendoUI02.zip
 
مطالب دوره‌ها
انتقال خودکار Data Annotations از مدل‌ها به ViewModelهای ASP.NET MVC به کمک AutoMapper
عموما مدل‌های ASP.NET MVC یک چنین شکلی را دارند:
public class UserModel
{
    public int Id { get; set; }
 
    [Required(ErrorMessage = "(*)")]
    [Display(Name = "نام")]
    [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام باید حداقل 3 و حداکثر 10 حرف باشد")]
    public string FirstName { get; set; }
 
    [Required(ErrorMessage = "(*)")]
    [Display(Name = "نام خانوادگی")]
    [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام خانوادگی باید حداقل 3 و حداکثر 10 حرف باشد")]
    public string LastName { get; set; }
}
 و ViewModel مورد استفاده برای نمونه چنین ساختاری را دارد:
public class UserViewModel
{
      public string FirstName { get; set; }
      public string LastName { get; set; }
}
مشکلی که در اینجا وجود دارد، نیاز به کپی و تکرار تک تک ویژگی‌های (Data Annotations/Attributes) خاصیت‌های مدل، به خواص مشابه آن‌ها در ViewModel است؛ از این جهت که می‌خواهیم برچسب خواص ViewModel، از ویژگی Display دریافت شوند و همچنین اعتبارسنجی‌های فیلدهای اجباری و بررسی حداقل و حداکثر طول فیلدها نیز حتما اعمال شوند (هم در سمت کاربر و هم در سمت سرور).
در ادامه قصد داریم راه حلی را به کمک جایگزین سازی Provider‌های توکار ASP.NET MVC با نمونه‌ی سازگار با AutoMapper، ارائه دهیم، به نحوی که دیگر نیازی نباشد تا این ویژگی‌ها را در ViewModelها تکرار کرد.


قسمت‌هایی از ASP.NET MVC که باید جهت انتقال خودکار ویژگی‌ها تعویض شوند

ASP.NET MVC به صورت توکار دارای یک ModelMetadataProviders.Current است که از آن جهت دریافت ویژگی‌های هر خاصیت استفاده می‌کند. می‌توان این تامین کننده‌ی ویژگی‌ها را به نحو ذیل سفارشی سازی نمود.
در اینجا IConfigurationProvider همان Mapper.Engine.ConfigurationProvider مربوط به AutoMapper است. از آن جهت استخراج اطلاعات نگاشت‌های AutoMapper استفاده می‌کنیم. برای مثال کدام خاصیت Model به کدام خاصیت ViewModel نگاشت شده‌است. این‌کارها توسط متد الحاقی GetMappedAttributes انجام می‌شوند که در ادامه‌ی مطلب معرفی خواهد شد.
public class MappedMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private readonly IConfigurationProvider _mapper;
 
    public MappedMetadataProvider(IConfigurationProvider mapper)
    {
        _mapper = mapper;
    }
 
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        var mappedAttributes =
            containerType == null ?
            attributes :
            _mapper.GetMappedAttributes(containerType, propertyName, attributes.ToList());
        return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);
    }
}

شبیه به همین کار را باید برای ModelValidatorProviders.Providers نیز انجام داد. در اینجا یکی از تامین کننده‌های ModelValidator، از نوع DataAnnotationsModelValidatorProvider است که حتما نیاز است این مورد را نیز به نحو ذیل سفارشی سازی نمود. در غیراینصورت error messages موجود در ویژگی‌های تعریف شده، به صورت خودکار منتقل نخواهند شد.
public class MappedValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private readonly IConfigurationProvider _mapper;
 
    public MappedValidatorProvider(IConfigurationProvider mapper)
    {
        _mapper = mapper;
    }
 
    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata,
        ControllerContext context,
        IEnumerable<Attribute> attributes)
    {
 
        var mappedAttributes =
            metadata.ContainerType == null ?
            attributes :
            _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes.ToList());
        return base.GetValidators(metadata, context, mappedAttributes);
    }
}

و در اینجا پیاده سازی متد GetMappedAttributes را ملاحظه می‌کنید.
ASP.NET MVC هر زمانیکه قرار است توسط متدهای توکار خود مانند Html.TextBoxFor, Html.ValidationMessageFor، اطلاعات خاصیت‌ها را تبدیل به المان‌های HTML کند، از تامین کننده‌های فوق جهت دریافت اطلاعات ویژگی‌های مرتبط با هر خاصیت استفاده می‌کند. در اینجا فرصت داریم تا ویژگی‌های مدل را از تنظیمات AutoMapper دریافت کرده و سپس بجای ویژگی‌های خاصیت معادل ViewModel درخواست شده، بازگشت دهیم. به این ترتیب ASP.NET MVC تصور خواهد کرد که ViewModel ما نیز دقیقا دارای همان ویژگی‌های Model است.
public static class AutoMapperExtensions
{
    public static IEnumerable<Attribute> GetMappedAttributes(
        this IConfigurationProvider mapper,
        Type viewModelType,
        string viewModelPropertyName,
        IList<Attribute> existingAttributes)
    {
        if (viewModelType != null)
        {
            foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.DestinationType == viewModelType))
            {
                var propertyMaps = typeMap.GetPropertyMaps()
                    .Where(propertyMap => !propertyMap.IsIgnored() && propertyMap.SourceMember != null)
                    .Where(propertyMap => propertyMap.DestinationProperty.Name == viewModelPropertyName);
 
                foreach (var propertyMap in propertyMaps)
                {
                    foreach (Attribute attribute in propertyMap.SourceMember.GetCustomAttributes(true))
                    {
                        if (existingAttributes.All(i => i.GetType() != attribute.GetType()))
                        {
                            yield return attribute;
                        }
                    }
                }
            }
        }
 
        if (existingAttributes == null)
        {
            yield break;
        }
 
        foreach (var attribute in existingAttributes)
        {
            yield return attribute;
        }
    }
}


ثبت تامین کننده‌های سفارشی سازی شده توسط AutoMapper

پس از تهیه‌ی تامین کننده‌های انتقال ویژگی‌ها، اکنون نیاز است آن‌ها را به ASP.NET MVC معرفی کنیم:
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes); 
 
    Mappings.RegisterMappings();
    ModelMetadataProviders.Current = new MappedMetadataProvider(Mapper.Engine.ConfigurationProvider);
 
    var modelValidatorProvider = ModelValidatorProviders.Providers
        .Single(provider => provider is DataAnnotationsModelValidatorProvider);
    ModelValidatorProviders.Providers.Remove(modelValidatorProvider);
    ModelValidatorProviders.Providers.Add(new MappedValidatorProvider(Mapper.Engine.ConfigurationProvider));
}
در اینجا ModelMetadataProviders.Current با MappedMetadataProvider جایگزین شده‌است.
در قسمت کار با ModelValidatorProviders.Providers، ابتدا صرفا همان تامین کننده‌ی از نوع DataAnnotationsModelValidatorProvider پیش فرض، یافت شده و حذف می‌شود. سپس تامین کننده‌ی سفارشی سازی شده‌ی خود را معرفی می‌کنیم تا جایگزین آن شود.


مثالی جهت آزمایش انتقال خودکار ویژگی‌های مدل به ViewModel

کنترلر مثال برنامه به شرح زیر است. در اینجا از متد Mapper.Map جهت تبدیل خودکار مدل کاربر به ViewModel آن استفاده شده‌است:
public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new UserModel { FirstName = "و", Id = 1, LastName = "ن" };
        var viewModel = Mapper.Map<UserViewModel>(model);
        return View(viewModel);
    }
 
    [HttpPost]
    public ActionResult Index(UserViewModel data)
    {
        return View(data);
    }
}
با این View که جهت ثبت اطلاعات مورد استفاده قرار می‌گیرد. این View، اطلاعات مدل خود را از ViewModel معرفی شده‌ی در ابتدای بحث دریافت می‌کند:
@model Sample12.ViewModels.UserViewModel
 
@using (Html.BeginForm("Index", "Home", FormMethod.Post, htmlAttributes: new { @class = "form-horizontal", role = "form" }))
{
    <div class="row">
        <div class="form-group">
            @Html.LabelFor(d => d.FirstName, htmlAttributes: new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(d => d.FirstName)
                @Html.ValidationMessageFor(d => d.FirstName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(d => d.LastName, htmlAttributes: new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(d => d.LastName)
                @Html.ValidationMessageFor(d => d.LastName)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="ارسال" class="btn btn-default" />
            </div>
        </div>
    </div>
}
در این حالت اگر برنامه را اجرا کنیم به شکل زیر خواهیم رسید:


در این شکل هر چند نوع مدل View مورد استفاده از ViewModel ایی تامین شده‌است که دارای هیچ ویژگی و Data Annotations/Attributes نیست، اما برچسب هر فیلد از ویژگی Display دریافت شده‌‌است. همچنین اعتبارسنجی سمت کاربر فعال بوده و برچسب‌های آن‌ها نیز به درستی دریافت شده‌اند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
بازخوردهای دوره
انتقال خودکار Data Annotations از مدل‌ها به ViewModelهای ASP.NET MVC به کمک AutoMapper
آیا از این روش میشه تمام Data Annotations های مدل رو برای ViewModel فرستاد؟ 
مثلا من توی مدل ام از ویژگی AdditionalMetadata استفاده کردم و توی View هم از کد زیر برای نمایش اطلاعات آنها استفاده میکنم.
@ModelMetadata.FromLambdaExpression(x => x.Name, ViewData).AdditionalValues["HelpTag"]
اما خطای زیر ارسال میشه:
The given key was not present in the dictionary 
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
در DynamicPermissionsAuthorizationHandler زمانیکه به rout data دسترسی پیدا کردید، مابقی تصمیم گیری‌ها بر اساس اطلاعات آن صورت می‌گیرند. بر روی سطر پس از آن، یک break-point را قرار دهید و محتوای این شیء را بررسی کنید. برای مثال در مورد razor pages می‌تواند چنین ساختاری را داشته باشد:
{
  "DataTokens":{},
  "Routers":[],
  "Values":{"page":"/Index","id":"101273123"}
}
بنابراین بله؛ در اینجا هم می‌توان بر اساس خاصیت Values و page و سایر پارامترها، در مورد دسترسی دادن به کاربر جاری، تصمیم گیری پویایی را انجام داد.
نظرات مطالب
استفاده از افزونه‌ی jsTree در ASP.NET MVC
- کدها را تصویری ارسال نکنید. در این حالت نه قابلیت جستجو دارند و نه قابلیت کپی سریع و پیگیری آن‌ها.
- این افزونه اطلاعات را با فرمت application/x-www-form-urlencoded ارسال می‌کند:
"dataType": "json",
"contentType": "application/x-www-form-urlencoded",
'data': function (node) {
  return { 'id': node.id, recordId: 7885, name: "Test" };
}
نظرات مطالب
امن سازی درخواست‌های ای‌جکسی برنامه‌های ASP.NET MVC 5.x در مقابل حملات CSRF
مقدارش قبلا تنظیم شده‌است. data ایی که به اون ارسال شده حاوی postId هست ({} یعنی یک شیء حاوی اطلاعات و خواص جاوا اسکریپتی). در اینجا فقط به صورت پویا یک خاصیت جدید به آن اضافه می‌شود. جاوا اسکریپت یک زبان dynamic هست. مثل واژه‌ی کلیدی dynamic در #C که بعدش می‌تونید خواصی را به دلخواه به این نوع اشیاء اضافه کنید. همان توضیحات مطلبی که لینک دادم.