مطالب
استفاده از Lambda Expression در پروژه های مبتنی بر WCF
نکته : آشنایی با مفاهیم پایه WCF برای فهم بهتر مفاهیم توصیه می‌شود.

امروزه استفاده از WCF در پروژه‌های SOA بسیار فراگیر شده است. کمتر کسی است که در مورد قدرت تکنولوژی WCF نشنیده باشد یا از این تکنولوژی در پروژه‌های خود استفاده نکرده باشد. WCF مدل برنامه نویسی یکپارچه مایکروسافت برای ساخت نرم‌افزارهای سرویس گرا است و برای توسعه دهندگان امکانی را فراهم می‌کند که راهکارهایی امن، و مبتنی بر تراکنش را تولید نمایند که قابلیت استفاده در بین پلتفرم‌های مختلف را دارند. قبل از WCF توسعه دهندگان پروژه‌های نرم افزاری برای تولید پروژه‌های توزیع شده باید شرایط موجود برای تولید و توسعه را در نظر می‌گرفتند. برای مثال اگر استفاده کننده از سرویس در داخل سازمان و بر پایه دات نت تهیه شده بود از .net remoting استفاده می‌کردند و اگر استفاده کننده سرویس از خارج سازمان یا مثلا بر پایه تکنولوژی J2EE بود از Web Service‌ها استفاده می‌شد. با ظهور WCF این تکنولوژی با هم تجمیع شدند(بهتر بگم تبدیل به یک تکنولوژی واحد شدند) و دیگر خبری از net remoting یا web service‌ها نیست.
  WCF با تمام قدرت و امکاناتی که داراست دارای نقاط ضعفی هم می‌باشد که البته این معایب (یا محدودیت) بیشتر جهت سازگار سازی سرویس‌های نوشته شده با سیستم‌ها و پروتکل‌های مختلف است.
برای انتقال داده‌ها از طریق WCF بین سیستم‌های مختلف باید داده‌های مورد نظر حتما سریالایز شوند که مثال هایی از این دست رو در همین سایت می‌تونید مطالعه کنید:
(^ )  و (^ ) و (^ )

با توجه به این که داده‌ها سریالایز می‌شوند، در نتیجه امکان انقال داده هایی که از نوع object  هستند در WCF وجود ندارد. بلکه نوع داده باید صراحتا ذکر شود و این نوع باید قابیلت سریالایز شدن را دارا باشد.برای مثال شما نمی‌تونید متدی داشته باشید که پارامتر ورودی آن از نوع delegate باشد یا کلاسی باشد که صفت [Serializable] در بالای اون قرار نداشته باشد یا کلاسی باشد که صفت DataContract برای خود کلاس و صفت DataMember برای خاصیت‌های اون تعریف نشده باشد. حالا سوال مهم این است اگر متدی داشته باشیم که پارامتر ورودی آن حتما باید از نوع delegate باشد چه باید کرد؟

برای تشریح بهتر مسئله یک مثال می‌زنم؟

سرویسی داریم برای اطلاعات کتاب ها. قصد داریم متدی بنوسیم که پارامتر ورودی آن از نوع Lambda Expression است تا Query مورد نظر کاربر از سمت کلاینت به سمت سرور دریافت کند و خروجی مورد نظر را با توجه به Query ورودی به کلاینت برگشت دهد.( متدی متداول در اکثر پروژه ها). به صورت زیر عمل می‌کنیم.

*ابتدا یک Blank Solution ایجاد کنید.

*یک ClassLibrary به نام Model ایجاد کنید و کلاسی به نام Book در آن بسازید .(همانطور که میبینید کلاس مورد نظر سریالایز شده است):

   [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Title { get; set; }
    }
* یک WCF Service Application ایجاد کنید
یک Contract برای ارتباط بین سرور و کلاینت می‌سازیم:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.ServiceModel;

namespace WcfLambdaExpression
{
    [ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression );
    }
}
متد GetByExpression دارای پارامتر ورودی expression است که نوع آن نیز Lambda Expression  می‌باشد. حال یک سرویس ایجاد می‌کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace WcfLambdaExpression
{    
    public class BookService : IBookService
    {        
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook 
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( Expression<Func<Book, bool>> expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

            return ListOfBook.AsQueryable().Where( expression );
        }       
    }
}
بعد از Build پروژه همه چیز سمت سرور آماده است. یک پروژه دیگر از نوع Console ایجاد کنید و از روش AddServiceReference سعی کنید که سرویس مورد نظر را به پروژه اضافه کنید. در هنگام Add Service Reference برای اینکه سرویس سمت سرور و کلاینت هر دو با یک مدل کار کنند باید از یک Reference assembly استفاده کنند و کافی است از قسمت Advanced گزینه Reuse types in referenced assemblies را تیک بزنید و assembly‌های مورد نظر را انتخاب کنید.( در این پروژه باید Model و System.Xml.Linq را انتخاب کنید)


به طور حتم با خطا روبرو خواهید شد. دلیل آن هم این است که امکان سریالایز کردن برای پارامتر ورودی expression میسر نیست.
خطای مربوطه به شکل زیر خواهد بود:
Type 'System.Linq.Expressions.Expression`1[System.Func`2[WcfLambdaExpression.Book,System.Boolean]]' cannot be serialized. 
Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  
If the type is a collection, consider marking it with the CollectionDataContractAttribute.  
See the Microsoft .NET Framework documentation for other supported types
حال چه باید کرد؟
روش‌های زیادی برای بر طرف کردن این محدودیت وجود دارد. اما در این پست روشی رو که خودم از اون استفاده می‌کنم رو براتون شرح می‌دهم.
در این روش باید از XElement استفاده شود که در فضای نام System.Linq.Xml قرار دارد. یعنی آرگومان ورودی سمت کلاینت باید به فرمت Xml سریالایز شود و سمت سرور دوباره دی سریالایز شده و تبدیل به یک Lambda Expression شود. اما سریالایز کردن Lambda Expression واقعا کاری سخت و طاقت فرساست . با توجه به این که در اکثر پروژه‌ها این متد‌ها به صورت Generic نوشته می‌شوند. برای حل این مسئله بعد از مدتی جستجو، کلاسی رو پیدا کردم که این کار رو برام انجام می‌داد. بعد از مطالعه دقیق و مشاهده روش کار کلاس، تغییرات مورد نظرم رو اعمال کردم و الان در اکثر پروژه هام دارم از این کلاس استفاده می‌کنم.
یک مثال از روش استفاده :
برای اینکه از این کلاس در هر دو پروژه (سرور و کلاینت) استفاده می‌کنیم باید یک Class Library جدید به نام Common بسازید و یک ارجاع از اون رو به هر دو پروژه سمت سرور و کلاینت بدید.
سرویس و Contract بالا رو به صورت زیر باز نویسی کنید.
[ServiceContract]
    public interface IBookService
    {
        [OperationContract]
        IEnumerable<Book> GetByExpression( XElement expression );
    }
و سرویس :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace WcfLambdaExpression
{
    public class BookService : IBookService
    {
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook
        {
            get;
            private set;
        }

        public IEnumerable<Book> GetByExpression( XElement expression )
        {
            ListOfBook.AddRange( new Book[] 
            {
                new Book(){Code = 1 , Title = "Book1"},
                new Book(){Code = 2 , Title = "Book2"},
                new Book(){Code = 3 , Title = "Book3"},
                new Book(){Code = 4 , Title = "Book4"},
                new Book(){Code = 5 , Title = "Book5"},
            } );

             Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            return ListOfBook.AsQueryable().Where( serializer.Deserialize( expression ) as Expression<Func<Book, bool>> );
        }
    }
بعد از Build پروژه از روش Add Service Reference استفاده کنید و می‌بینید که بدون هیچ گونه مشکلی سرویس مورد نظر به پروژه Console اضافه شد. برای استفاده سمت کلاینت به صورت زیر عمل کنید.

using System;
using System.Linq.Expressions;
using TestExpression.MyBookService;

namespace TestExpression
{
    class Program
    {
        static void Main( string[] args )
        {
            BookServiceClient bookService = new BookServiceClient();

            Expression<Func<Book, bool>> expression = x => x.Code > 2 && x.Code < 5;

            Common.ExpressionSerializer serializer = new Common.ExpressionSerializer();

            bookService.GetByExpression( serializer.Serialize( expression ) );
        }
    }
}
بعد از اجرای پروژه، در سمت سرور خروجی‌های زیر رو مشاهده می‌کنیم.

خروجی هم به صورت زیر خواهد بود:

دریافت سورس کامل Expression-Serialization
مطالب
ارسال فایل و تصویر به همراه داده‌های دیگر از طریق jQuery Ajax
یکی از سوالات رایجی که زیاد پرسیده می‌شود این است که چطور باید یک تصویر را به همراه فیلدهای دیگر به سمت سرور ارسال کرد. اکثر مثال‌های آپلود عکس بدین صورت هستند که از یک کتابخانه پویا استفاده می‌کنند که همان لحظه عکس را به سمت سرور ارسال میکنند. ولی یک مثال و کاربرد بسیار ساده این است که من میخواهم اطلاعات فرم و عکس Input File را به همراه یکدیگر همزمان ارسال نمایم که اتفاقا این مسئله به شدت هم اتفاق می‌افتد. برای مثال شما اطلاعات یک دانش آموز را وارد سیستم میکنید که به صورت ایجکسی به سمت سرور ارسال می‌شوند و حالا نیاز است که تصویر دانش آموز هم وارد سیستم شود که ارسال این تصویر نیز از طریق یک input File رخ می‌دهد. نحوه کار با یک input File در سمت سرور در سایت جاری پرداخته شده است که برای پرهیز از تکرار از آن خودداری میکنم.

XMLHttpRequest رابطی است که به شما امکان نقل و انتقالات را از سمت کاربر، به سمت سرور و سپس دریافت پاسخ آن را می‌دهد. این رابط طوری طراحی شده‌است که دیگر برای این جابجایی نیازی به بارگزاری مجدد کل صفحه نباشد و قسمتی از اطلاعات صفحات به روز شوند، مزاحمتی برای کاربر ایجاد نشود. به همین دلیل از این رابط، در پشت صحنه‌های عملیات ایجکسی استفاده زیادی می‌شود. در این مقاله با استفاده از خصوصیتی به نام request.IsAjax بررسی می‌شود که آیا درخواست رسیده به سرور از نوع ایجکسی است یا خیر. اگر به سورس نوشته شده این متد نگاه دقیق‌تری بیندازیم، متوجه می‌شویم کاری که این متد انجام می‌دهد، در واقع در یک خط خلاصه می‌شود و آن بررسی هدری برای وجود درخواست از نوع XMLHttpRequest است:
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
برای اطلاعات بیشتر در مورد این رابط، خصوصیت‌ها، متدهایش و پشتیبانی سایر مرورگرها از این خواص، بهتر است به صفحه مستندات موزیلا نگاهی بیندازید.

یکی از متدهای این رابط، متد ارسال آن (send) می‌باشد که میتواند رابطی به نام formData را انتقال دهد و این رابط از نوع مجموعه‌ای از کلید و مقدارهاست. این رابط زمانی به کار گرفته می‌شود که انکدینگ فرم خود را بر روی multipart/form-data قرار داده باشید. این ساختار می‌تواند توسط دستور for of بررسی گردد. برای آشنایی بیشتر با متدهای آن این صفحه را مطالعه فرمایید.
هنگام ارسال فایل در حالت postback، ما فرم را بر روی multipart قرار می‌دهیم تا امکان ارسال آن توسط formData مهیا شود. ولی از آنجاکه ما از ایجکس استفاده می‌کنیم، بهتر است که خودمان مستقیما از این ساختار استفاده کنیم.

بخشی از فرم Html
<div>
  <label>تصویر</label>
  <div>
    <input id="picture" type="file" data-buttonText="انتخاب تصویر">
  </div>
</div>

<div>
  <label>کد ملی</label>
  <div>
    <input id="txtNationalCode" required="" maxlength="10" type="text">
  </div>
</div>

<div>
  <label>نام</label>
  <div>
    <input id="txtName" type="text" maxlength="50" required="">
  </div>
</div>

<div class="form-group">
  <div class="col-sm-4 col-sm-offset-2">
    <button class="btn btn-primary" id="btnSubmit" type="submit">ذخیره</button>
    <button class="btn btn-white" id="btnClear" type="submit">لغو</button>
  </div>
</div>
برای زیباسازی  کار، برای المان input File در بالا، از کتابخانه Bootstrap FileStyle استفاده شده‌است.
سپس کد جی کوئری زیر را می‌نویسیم:
 var formData = new FormData();
        formData.append('FirstName', $("#txtName").val());
        formData.append('NationalCode', $("#txtNationalCode").val());

        jQuery.each($('#picture')[0].files, function (i, file) {
            formData.append('picture-'+i, file);
        });

 $.ajax({
            type: "POST",
            dataType: "json",
            url: address,
            data: formData,
            success:
                function (data) {
                //.....

                },
            error:
                function (data) {
                  //......
                }
        });
در کد بالا ابتدا یک شیء FormData ایجاد می‌شود و سپس کلیدو مقدارهایی نیز به آن انتساب داده می‌شوند. در صورتیکه Input File شما، امکان آپلود چندین فایل همزمان را می‌دهد، می‌توانید با استفاده از حلقه‌ی مورد نظر، یکی یکی آن‌ها را به این شیء اضافه کنید و به ترتیب اسامی pictue-0 ,picture-1 و ... به آن‌ها انتساب داده می‌شود. در نهایت تنها کاری که لازم است انجام دهید این است که روال همیشگی را طی کنید و این شیء را به عنوان data، در اختیار متد ajax قرار دهید تا ارسال شود.

توجه به این نکته ضروری است و با توجه کدهایی که در نت دیدم و بسیاری از آن حتی به عنوان پاسخ صحیح در نظر گرفته شده بودند این است که شیء FormData شامل هیچ سازنده‌ای نیست و باید با استفاده از متد append آن‌ها را اضافه کنید.
نظرات مطالب
بهبود SEO در ASP.NET MVC
نکته‌ای مهم برای کاهش SEO جماعت کپی پیست کار
اگر محتوای سایت شما عینا در سایت‌های دیگر کپی پیست شده، چگونه می‌توانید به گوگل اعلام کنید که محتوای اصلی کدام است؟ کدام یکی کپی است و کدام یک متعلق به شما است؟ برای این منظور باید ذیل مطالب خودتان تنظیمات مربوط به گوگل پلاس خود را نیز قرار دهید:
 <a rel="author" href="https://plus.google.com/your_g_plus_id?rel=author">G+</a>
سپس در گوگل پلاس خود اعلام کنید که در این سایت مشارکت دارید (در قسمت Contributor to).
نظرات مطالب
مروری بر کاربردهای Action و Func - قسمت چهارم
این تکنیک و مقاله، یکی از مطالب Must Read سال هست. به شخصه از این تکنیک در توسعه‌ی کامپوننت‌های ASP.NET MVC استفاده می‌کنم. کلاً تکنیک Fluent که برادر نصیری فعلاً در دو مقاله به اون پرداختند، انعطاف پذیری بسیاری به برنامه‌ها میده. مثلاً شبیه سازی روال RowDataBound کنترل GridView در Web Forms، در بستر MVC با استفاده از یک Action.
به نظر من کمبودی که ASP.NET MVC در حال حاضر داره، داشتن مجموعه ای غنی از کامپوننت‌های توکار هست که فکر می‌کنم در نسخه‌های آینده، مایکروسافت این نقیصه رو بر طرف می‌کنه، شاید با مشارکت شرکت‌های دیگه مثل Telerik. 
اشتراک‌ها
دوره 4 ساعته شروع به کار با NET MAUI.
Learn .NET MAUI - Full Course for Beginners | Tutorial for iOS, Android, Mac, Windows in C#
Let's start our journey together to build beautiful native cross-platform apps for iOS, Android, macOS, and Windows with .NET MAUI, C#, and Visual Studio! In this full workshop, I will walk you through everything you need to know about .NET MAUI and building your very first app. You will learn the basics including how to build user interfaces with XAML, how MVVM and data binding simplify development, how to navigate between pages, access platform features like geolocation, optimize data collections, and theme your app for light theme and dark theme. This course has everything you need to learn the basics and set you up for success when building apps with .NET MAUI!

Chapters:
00:00:00 - Intro to the .NET MAUI Workshop
00:04:10 - What is .NET MAUI & How to Install
00:06:25 - Workshop overview
00:08:00 - First .NET MAUI app & Architecture (slides)
00:21:40 - Get code to build your first .NET MAUI app
00:25:00 - .NET MAUI Project Walkthrough
00:29:40 - Start to build first .NET MAUI app
00:56:48 - Intro to MVVM (slides)
01:09:30 - Implementing INotifyPropertyChanged
01:22:30 - .NET Community Toolkit for MVVM (Source Generators)
01:32:30 - HTTP REST Calls & JSON Deserialization
01:43:00 - ICommand in .NET MAUI
01:59:30 - Create our UI with XAML & MVVM
02:16:20 - Navigation in .NET MAUI (slides)
02:25:20 - Implementing Navigation in .NET MAUI & Passing Parameters
02:46:00 - Building a details UI with XAML & MVVM
02:54:10 - Modal, Back Navigation, & More
02:58:20 - Access Platform APIs in .NET MAUI (slides)
03:02:53 - Platform API Integration - Connectivity
03:08:30 - Geolocation & Permissions Implementation
03:18:50 - Open Map Integration
03:22:40 - Platform Specifics - iOS Safe Area
03:25:50 - CollectionView & RefreshView Overview (slides)
03:34:00 - Implementing Pull-to-Refresh
03:40:00 - CollectionView Layouts - Grids and more
03:41:30 - CollectionView EmptyView
03:45:00 - App Resources, Styles, and Themes (slides)
03:56:20 - Implementing Light & Dark Mode
04:06:00 - Thanks, wrap-up, and resources 
دوره 4 ساعته شروع به کار با NET MAUI.
نظرات مطالب
EF Code First #4
سلام و وقتتون بخیر
ممنون بابت مطلبتون.من دقیقا طبق مطلب شما Migrations رو پیاده کردم.اما یه مشکل دارم.اونم اینه که وقتی دیتابیسم برا اولین بار میخواد ساخته بشه ،خطای لاگین میده:
Cannot open database "Test" requested by the login. The login failed.
Login failed for user 'sa'.

البته این خطا فقط در صورتی داده میشه که مهاجرت خودکار فعال شده باشه،اگر این خط رو کامنت کنم دیتابیس بدون مشکل ساخته میشه:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<CommonContext, Configuration>());
با توجه به مطلب شما حدس می‌زدم به خاط نکته ای باشه که در مورد جدول  dbo.__MigrationHistory  گفتید.اما با استفاده از این لینک جدول رو از اول به صورت غیر سیستمی میسازم اما بازم مشکل دارم.البته دیتا بیس ایجاد میشه،فقط جدول dbo.__MigrationHistory  رو میسازه و بعدش خطایی که گفتم رو میده.
ممنون.
مطالب
استفاده از پلاگین DataTables کتابخانه jQuery در برنامه‌های ASP.NET Core
datatable js، کتابخانه‌ای جهت ساخت جداول است و نسبت به رقیب اصلی خودش یعنی kendo telerik، از سادگی بیشتری برخوردار هست و امکانات خوبی هم دارد.

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

در ابتدا به بررسی کد‌ها و تغییرات سمت فرانت‌اند و صفحه‌ی cshtml می‌پردازیم:
1- تابع Ajax ای که وظیفه‌ی دریافت اطلاعات را دارد، به کل پاک کنید. چون Ajax به صورت یک آبجکت، به درون خود دیتاتیبل منتقل خواهد شد.
2- در صفحه خود، کد زیر را قرار دهید (جهت جلوگیری از 400 bad request) که این کار فقط برای هندلر‌های razor page و یا controller نیاز است و اگر از API استفاده میکنید، مسلما نیازی به این مدل تنظیمات نیست.
@Html.AntiForgeryToken()
3- سپس کد زیر را به startup خود اضافه کنید (در قسمتی که دارید اینترفیس‌ها را ثبت میکنید):
//Post in Ajax
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
4- حالا نوبت کانفیگ‌های دیتاتیبل هست:
function initDataTables() {
        table.destroy();
        table = $("#tblJs").DataTable({
          processing: true,
          serverSide: true,
          filter: true,
          ajax: {
            url: '@Url.Page("yourPage","yourHandler")',
            beforeSend: function (xhr) {
              xhr.setRequestHeader("XSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
            },
            type: "POST",
            datatype: "json"
          },
          language: {
            url: "/Persian.json"
          },
          responsive: true,
          select: true,

          columns: scheme,
          select: true,
        });
      }
5-کد بالا، کل تابعی را نشان میدهد که وظیفه‌ی ساخت دیتاتیبل را دارد؛ ولی شما تنها نیاز دارید قسمت زیر را اضافه کنید:
processing: true,
          serverSide: true,
          filter: true,
          ajax: {
            url: '@Url.Page("yourPage","yourHandler  ")',
            beforeSend: function (xhr) {
              xhr.setRequestHeader("XSRF-TOKEN",
                $('input:hidden[name="__RequestVerificationToken"]').val());
            },
            type: "POST",
            datatype: "json"
          },

حالا باید کد‌های سمت سرور را بنویسیم. برای این منظور باید ابتدا مقادیری را که دیتاتیبل برای ما ارسال میکند، از ریکوئست دریافت کنیم.
6- کل دیتایی که دیتا تیبل برای ما میفرستد، به مدل زیر خلاصه میشود:
public class FiltersFromRequestDataTable
    {
        public string length { get; set; }
        public string start { get; set; }
        public string sortColumn { get; set; }
        public string sortColumnDirection { get; set; }
        public string sortColumnIndex { get; set; }
        public string draw { get; set; }
        public string searchValue { get; set; }
        public int pageSize { get; set; }
        public int skip { get; set; }
    }
نکته‌ی مهم این است که پراپرتی‌ها باید با اسم کوچک به سمت فرانت‌اند ارسال شوند.
* (من از razor page  استفاده میکنم؛ ولی مسلما در controller هم به همین شکل و راحت‌تر خواهد بود) 
7- سپس داده‌های ارسال شده‌ی توسط دیتاتیبل، به سمت سرور را با استفاده از متد زیر دریافت میکنیم:
 public static void GetDataFromRequest(this HttpRequest Request, out FiltersFromRequestDataTable filtersFromRequest)
        {
            //TODO: Make Strings Safe String
            filtersFromRequest = new();

            filtersFromRequest.draw = Request.Form["draw"].FirstOrDefault();
            filtersFromRequest.start = Request.Form["start"].FirstOrDefault();
            filtersFromRequest.length = Request.Form["length"].FirstOrDefault();
            filtersFromRequest.sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault();
            filtersFromRequest.sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault();
            filtersFromRequest.searchValue = Request.Form["search[value]"].FirstOrDefault();
            filtersFromRequest.pageSize = filtersFromRequest.length != null ? Convert.ToInt32(filtersFromRequest.length) : 0;
            filtersFromRequest.skip = filtersFromRequest.start != null ? Convert.ToInt32(filtersFromRequest.start) : 0;
            filtersFromRequest.sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault();

            filtersFromRequest.searchValue = filtersFromRequest.searchValue?.ToLower();
        }
8- نحوه‌ی استفاده از این متد در handler یا action مورد نظر:
Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);

9- با استفاده از متد زیر، مقادیر مورد نیاز دیتاتیبل را به آن ارسال می‌کنیم:
 public static PaginationDataTableResult<T> ToDataTableJs<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest)
        {
            int recordsTotal = source.Count();
            CofingPaging(ref filtersFromRequest, recordsTotal);
            var result = new PaginationDataTableResult<T>()
            {
                draw = filtersFromRequest.draw,
                recordsFiltered = recordsTotal,
                recordsTotal = recordsTotal,
                data = source.OrderByIndex(filtersFromRequest).Skip(filtersFromRequest.skip).Take(filtersFromRequest.pageSize).ToList()
            };

            return result;
        }

        private static void CofingPaging(ref FiltersFromRequestDataTable filtersFromRequest, int recordsTotal)
        {
            if (filtersFromRequest.pageSize == -1)
            {
                filtersFromRequest.pageSize = recordsTotal;
                filtersFromRequest.skip = 0;
            }
        }
private static IEnumerable<T> OrderByIndex<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest)
        {
            var props = typeof(T).GetProperties();
            string propertyName = "";
            for (int i = 0; i < props.Length; i++)
            {
                if (i.ToString() == filtersFromRequest.sortColumnIndex)
                    propertyName = props[i].Name;
            }

            System.Reflection.PropertyInfo propByName = typeof(T).GetProperty(propertyName);
            if (propByName is not null)
            {
                if (filtersFromRequest.sortColumnDirection == "desc")
                    source = source.OrderByDescending(x => propByName.GetValue(x, null));
                else
                    source = source.OrderBy(x => propByName.GetValue(x, null));
            }

            return source;
        }
که باهر دیتاتایپی کار می‌کند و خودش به صورت خودکار، عملیات مرتب‌سازی را انجام می‌دهد (ابدایی خودم)
10- نحوه استفاده در هندلر یا اکشن:
var result =  query.ToDataTableJs(filtersFromRequest);
return new JsonResult(result);
یک نکته: پراپرتی‌های شما باید باحروف کوچک باشد وگرنه در سمت جاوااسکریپت، خطای undefined را مشاهده خواهید کرد. در این حالت باید پراپرتی‌ها را با حروف کوچک شروع کنید؛ ولی اگر دارید با کتابخانه‌ی newtonSoft  و jsonCovert سریالایز میکنید، میتوانید از این attribute بالای پراپرتی‌ها استفاده کنید: [JsonProperty("name")]
درکل باید یک iqueryrable را آماده و به متد ToDataTableJs ارسال کنید.

- برای سرچ هم در column‌ها هم  میتوانید به شکل زیر عمل کنید.
ابتدا دو متد زیر را به یک کلاس static اضافه کنید:
 public static IEnumerable<TSource> WhereSearchValue<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            return source.Where(predicate);
        }
        public static bool ContainsSearchValue(this string source, string toCheck)
        {
            return source != null && toCheck != null && source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
        }
بعد به این شکل از آن‌ها بین ایجاد iqueryrable  و جایی که متد todatatableJs فراخوانی می‌شود، استفاده کنید:
if (!string.IsNullOrEmpty(filtersFromRequest.searchValue))
query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();
برای افزایش کارآیی بهتر است مدل اصلی را به ویوو ارسال نکنید و از همان اول یک IQueryrable از جنس ویوومدل یا dto داشته باشید و این سرچ را هم بر روی همان انجام دهید.

کد کامل هندلر یا action  (که ترکیب کد‌های بالا هستش):
 public JsonResult OnPostList()
        {
            Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);
            var query = _Repo.GetQueryable().Select(x => new VmAdminList()
            {
                title = x.Title,
            }
            );

            if (!string.IsNullOrEmpty(filtersFromRequest.searchValue))
                query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();

            var result =  query.ToDataTableJs(filtersFromRequest);
            return new JsonResult(result);
        }

چند نکته:
1- ممکن‌است که بخواهید یکسری فیلتر را بجز مقادیر پیش فرض به سمت سرور ارسال کنید. برای اینکار کد زیر را به قسمت Ajax  فرانت‌اند اضافه کنید:
 data: function (d) {
                    d.parentId = parentID;
                    d.StartDateTime= StartDateTime;
                },
 و آن‌ها را به این شکل در سمت سرور دریافت کنید:
if (!int.TryParse(Request.Form["parentId"].FirstOrDefault(), out int parentId))
                throw new NullReferenceException();
2- در کانفیگ‌های Ajax مربوط به دیتاتیبل، دیگر کلید Success را نداریم؛ ولی به این شکل میتوانید این قسمت را شبیه سازی کنید:
 dataSrc: function (json) {
                    $("#count").val(json.data.length);
                    var sum = 0;
                    json.data.forEach(function (item) {
                        if (!isNullOrEmpty(item.credit))
                            sum += parseInt(item.credit);
                    })
                    $("#sum").val(separate(sum));

                    return json.data;
                }
که return آن الزامی هست؛ وگرنه به خطا میخورید.

تا به اینجا کار کاملا تمام شده؛ ولی من برای داینامیک کردن schema و column‌ها هم کلاسی را نوشته‌ام که فکر میکنم کار را راحت‌تر کند. چون شما برای تعداد ستون‌ها باید یک آبجکت را به شکل زیر تعریف کنید:
columns: [
        { data: 'name' },
        { data: 'position' },
        { data: 'salary' },
        { data: 'office' }
    ]
در اینجا اگر کلید‌ها و یا ستون‌ها (<th>) جابجا باشند، خطا می‌دهد و توسعه را بعدا سخت می‌کند؛ چون بعد هر بار تغییر، باید دستی این آبجکت‌ها و ستون‌ها را هم جابجا کنید. ولی با استفاده از کد‌های زیر، خودش به صورت داینامیک تولید می‌شود. کدزیر این کار رو انجام می‌دهد:
public class JsDataTblGeneretaor<T>
    {
        public readonly DataTableSchemaResult DataTableSchemaResult = new();
        public JsDataTblGeneretaor<T> CreateTableSchema()
        {
            var props = typeof(T).GetProperties();

            foreach (var prop in props)
            {
                DataTableSchemaResult.SchemaResult.Add(new()
                {
                    data = prop.Name,
                    sortable = (prop.PropertyType == typeof(int)) || (prop.PropertyType == typeof(bool)) || (prop.PropertyType == typeof(DateTime)),
                    width = "",
                    visible = (prop.PropertyType != typeof(DateTime))
                });
            }

            return this;
        }

        public JsDataTblGeneretaor<T> CreateTableColumns()
        {
            var props = typeof(T).GetProperties();

            CustomAttributeData displayAttribute;

            foreach (var prop in props)
            {
                string displayName = prop.Name;
                displayAttribute = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "DisplayAttribute");
                if (displayAttribute != null)
                {
                    displayName = displayAttribute.NamedArguments.FirstOrDefault().TypedValue.Value.ToString();
                }

                DataTableSchemaResult.Colums.Add(displayName);
            }

            return this;
        }

        public JsDataTblGeneretaor<T> AddCustomSchema(string data, bool? sortable = null, bool? visible = null, string width = null, string className = null)
        {
            if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any())
                return this;

            foreach (var item in DataTableSchemaResult.SchemaResult.Where(x => x.data == data))
            {
                if (sortable != null)
                    item.sortable = sortable.Value;

                if (visible != null)
                    item.visible = visible.Value;

                if (width != null)
                    item.width = width;

                if (className != null)
                    item.className = className;
            }

            return this;
        }
        public JsDataTblGeneretaor<T> SerializeSchema()
        {
            if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any())
                return this;

            DataTableSchemaResult.SerializedSchemaResult = JsonSerializer.Serialize(DataTableSchemaResult.SchemaResult);

            return this;
        }
    }
    public class DataTableSchema
    {
        public string data { get; set; }
        public bool sortable { get; set; }
        public string width { get; set; }
        public bool visible { get; set; }
        public string className { get; set; }
    }
    public class DataTableSchemaResult
    {
        public readonly List<DataTableSchema> SchemaResult = new();
        public readonly List<string> Colums = new();
        public string SerializedSchemaResult = "";
    }
متد CreateTableSchema، آبجکت هایی را که دیتاتیبل نیاز دارد، ایجاد می‌کند (فرقی ندارد مدلت شما از چه جنسی باشد) که شامل یک لیست از آبجکت‌هاست و شما میتوانید بااستفاده از متد AddCustomSchema، آن را سفارشی سازی کنید؛ مثلا بگوئید فلان کلید نمایش داده نشود و یا عرضش را مشخص کنید و ...  
متد CreateTableColumns  خیلی ساده هست و فقط یک لیست از استرینگ‌ها را برمیگرداند.
SerializeSchema هم که لیست آبجکت‌های مورد نیاز دیتاتیبل را سریالایز می‌کند.

نحوه استفاده:
در متد آغازین برنامه باید این کلاس را صدا بزنید و با هر روشی که دوست دارید، به view یا razor page ارسال کنید:
public void OnGet()
        {
            //Create Data Table Js Schema and Columns Dynamicly
            JsDataTblGeneretaor<yourVM> tblGeneretaor = new();
            DataTableSchemaResult = tblGeneretaor.CreateTableColumns().CreateTableSchema().SerializeSchema().DataTableSchemaResult;
        }

نحوه سفارشی سازی:
.AddCustomSchema("yourProperty",visible:false)
که به سمت View ارسال می‌شودو حالا نحوه‌ی استفاده کردن از scheme ساخته شده:
var scheme = JSON.parse('@Html.Raw(Model.DataTableSchemaResult.SerializedSchemaResult)')
و استفاده‌ی از آن در گزینه‌های دیتاتیبل  columns: scheme,

نحوه ساخت ستون‌ها در view:
@foreach (var col in Model.DataTableSchemaResult.Colums)
              {
                <th>@col</th>
              }

مطالب
آموزش QUnit #1
مقدمه:
تست و آزمایش کد برنامه‌ها و وب سایت‌هایمان، بهترین راه کاهش خطا و مشکلات آنها بعد از انتشار است. از جمله روش‌های موجود، تست واحد است که ویژوال استادیو نیز از آن برای پروژه‌های دات نت پشتیبانی می‌کند. با افزایش روز افزون کتابخانه‌های جاوا اسکریپتی و جی کوئری، نیاز به تست کد‌های جاواسکریپتی نیز بیشتر به نظر می‌رسد و بهتر است تست واحد و آزمایش شوند. اما برخلاف کدهای #C و ASP.NET تست کد‌های جاوا اسکریپت، مخصوصا زمانی که به دستکاری عناصر DOM می‌پردازیم و یا رویداد‌های درون صفحه وب را با استفاده از جی کوئری می‌نویسیم، حتی اگر در فایل جداگانه‌ای نوشته شود، این بدان معنی نیست که آماده تست واحد است و ممکن است امکان نوشتن تست وجود نداشته باشد.
بنابراین چه چیزی یک تست واحد است؟ در بهترین حالت توابعی که مقداری را برمی گردادنند، بهترین حالت برای تست واحد است. اما در بیشتر موارد شما نیاز دارید تا تاثیر کد را بر روی عناصر صفحه نیز مشاهد نمایید.
ساخت تست واحد
برای تست پذیری بهتر، توابع جاوا اسکریپت و هر کد دیگری، آن را می‌بایست طوری بنویسید که مقادیر تاثیر گذار در اجرای تابع به عنوان ورودی تابع در نظر گرفته شده باشند و همیشه نتیجه به عنوان خروجی تابع برگردانده شود؛ قطعه کد زیر را در نظر بگیرید:
 function prettyDate(time){
    var date = new Date(time || ""),
      diff = (((new Date()).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff == 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  }
تابع pertyDate اختلاف زمان حال را نسبت به زمان ورودی، بصورت یک رشته برمی گرداند. اما در اینجا مقدار زمان حال، در خط سوم، در خود تابع ایجاد شده است و در صورتی که بخواهیم برای چندین مقدار آن را تست کنیم زمان حال متفاوتی در نظر گرفته می‌شود و حداکثر، زمان 31 روز قبل را نمایش داده و در بقیه تاریخ ها undefined را بر می‌گرداند. برای تست واحد، چند تغییر می‌دهیم.
بهینه سازی، مرحله اول:
پارامتری به عنوان مقدار زمان جاری برای تابع در نظر می‌گیریم و تابع را جدا کرده و در یک فایل جداگانه قرار می‌دهیم. فایل prettydate.js بصورت زیر خواهد شد.
function prettyDate(now, time){
  var date = new Date(time || ""),
    diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
    day_diff = Math.floor(diff / 86400);
 
  if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
    return;
 
  return day_diff == 0 && (
      diff < 60 && "just now" ||
      diff < 120 && "1 minute ago" ||
      diff < 3600 && Math.floor( diff / 60 ) +
        " minutes ago" ||
      diff < 7200 && "1 hour ago" ||
      diff < 86400 && Math.floor( diff / 3600 ) +
        " hours ago") ||
    day_diff == 1 && "Yesterday" ||
    day_diff < 7 && day_diff + " days ago" ||
    day_diff < 31 && Math.ceil( day_diff / 7 ) +
      " weeks ago";
}
حال یک تابع برای تست داریم، چند تست واحد واقعی می‌نویسیم
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <script src="prettydate.js"></script>
  <script>
  function test(then, expected) {
    results.total++;
    var result = prettyDate("2013/01/28 22:25:00", then);
    if (result !== expected) {
      results.bad++;
      console.log("Expected " + expected +
        ", but was " + result);
    }
  }
  var results = {
    total: 0,
    bad: 0
  };
  test("2013/01/28 22:24:30", "just now");
  test("2013/01/28 22:23:30", "1 minute ago");
  test("2013/01/28 21:23:30", "1 hour ago");
  test("2013/01/27 22:23:30", "Yesterday");
  test("2013/01/26 22:23:30", "2 days ago");
  test("2012/01/26 22:23:30", undefined);
  console.log("Of " + results.total + " tests, " +
    results.bad + " failed, " +
    (results.total - results.bad) + " passed.");
  </script>
</head>
<body>
 
</body>
</html>
در کد بالا یک تابع بدون استفاده از Qunit برای تست واحد نوشته ایم که با آن تابع prettyDate را تست می‌کند. تابع test مقدار زمان حال و رشته خروجی را گرفته  و آن را با تابع اصلی تست می‌کند در آخر تعداد تست ها، تست‌های شکست خورده و تست  های پاس شده گزارش داده می‌شود.
خروجی می‌تواند مانند زیر باشد:

Of 6 tests, 0 failed, 6 passed.
Expected 2 day ago, but was 2 days ago. 
f 6 tests, 1 failed, 5 passed.