نظرات مطالب
خودکار کردن تعاریف DbSetها در EF Code first
الف)
- تمام مدل‌های شما باید از کلاس مشخصی مثلا BaseEntity مشتق شوند.
- یا در تنظیمات برنامه مشخص کنید در حین Reflection چه فضای نامی باید جستجو شود.
- و یا مثلا مانند ASP.NET MVC اگر کلاسی نامش به عبارت خاصی ختم شد و همچنین از کلاس پایه خاصی نیز مشتق شده بود آنگاه بررسی شود.
ب)
- مرحله بعد اندکی ویرایش متد loadEntities است جهت خواندن مدل‌های واقع شده مثلا در یک فضای نام خاص یا مشتق شده از یک کلاس پایه خاص و سپس افزودن خودکار آن‌ها به modelBuilder همانند چیزی که در مثال فوق پیاده سازی شده.
در مثال بالا فقط یک اسمبلی جستجو می‌شود؛ اگر نیاز است تمام اسمبلی‌های بارگذاری شده در برنامه جستجو شوند، روش کار مراجعه به AppDomain است:
foreach (Assembly currentassembly in AppDomain.CurrentDomain.GetAssemblies()) 
{
   Type t = currentassembly.GetType("typeName", false, true);
    if (t != null) {return currentassembly.FullName;}
}
مطالب
الگوی طراحی Null Object

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

الگوی طراحی Null Object این مشکل را حل می‌کند که جای پاس دادن شیء Null Reference بجای شیء ای که واقعا به آن وابستگی وجود دارد و باید هر بار قبل استفاده‌ی از آن بررسی کنیم که آیا آن شیء ای که داریم با آن کار می‌کنیم نال است یا خیر، کلاسی خاصی را بسازیم که یک وابستگی غیر کاربردی است. به این معنا که قرار نیست هیچ کاری را انجام دهد و عملا یک non-functional Dependency است. این کلاس یا یک اینترفیس خاصی را پیاده سازی می‌کند و یا اینکه از یک کلاس انتزاعی ارث بری خواهد کرد؛ ولی هیچ عملکرد خاصی را نخواهد داشت. به این معنا که متدها و پراپرتی‌های این کلاس کاری را انجام نداده و یک مقدار پیشفرض و یا یک مقدار خاصی را برگشت خواهند داد. این روش به ساده سازی کد کمک خواهد کرد، چون می‌توان بدون انجام پیش شرط‌هایی مانند بررسی نال بودن یا نبودن یک شیء وابسته، از آن استفاده کرد.

این الگوی طراحی معمولا همراه با دیگر الگوهای طراحی مورد استفاده قرار می‌گیرد. بهینه‌تر است که خود کلاس Null Object به صورت Singleton پیاده سازی شود. مزیت این کار در این است که چون شیء ساخته شده از این کلاس، نه کار خاصی را انجام می‌دهد و نه حالت خاصی را نگه می‌دارد، پس ساختن شیءای از آن عملا ضرورتی نداشته و هیچگونه ارزشی ندارد و فقط سرباری را بر روی نرم افزار قرار می‌دهد. پس سزاوار است فقط به یک شیء از این کلاس اکتفا کرد و هر بار همان شیء را برگشت داد. الگوی دیگری که غالبا از الگوی Null Object در آن استفاده می‌شود، الگوی Strategy است. زمانیکه یکی از استراتژی‌ها این باشد که کار خاصی را انجام نداد و یا استراتژی مورد نظر عملکردی نداشته باشد، از الگوی Null Object استفاده می‌کنیم. الگوی دیگری که از الگوی Null Object زیاد استفاده می‌کند، الگوی Factory است. برای مثال هنگامیکه بخواهیم بر طبق شرایط برنامه یک شیء Null Reference را بسازیم و برگردانیم، از الگوی Null Object استفاده خواهیم کرد.

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


پیاده سازی الگوی طراحی Null Object

کلاس دیاگرام زیر چگونگی پیاده سازی این الگو را نشان می‌دهد. در ادامه قصد داریم بخش‌های مختلف این دیاگرام را توضیح دهیم.

Client : این کلاس دارای یک وابستگی به یک کلاس دیگر است که در بعضی مواقع نیازی به این وابستگی پیدا نمی‌کند و در صورتیکه به کارکرد اصلی وابستگی نیاز پیدا نکند، متدهای داخل کلاس Null Object را اجرا می‌کند.

DependencyBase : این قسمت کلاس پایه‌ای است که به صورت Abstract بوده و شامل همه وابستگی‌هایی است که ممکن است Client به آن وابسته باشد. همچنین این بخش، کلاس پایه‌ی کلاس Null Object هم است. شایان ذکر است که بجای استفاده از کلاس Abstract می‌توان از یک Interface هم استفاده کرد؛ چون این کلاس هیچ عملکرد مشترکی را برای زیر کلاس‌هایش پیاده سازی نمی‌کند.

Dependency : این کلاس یک عملکرد واقعی از یک وابستگی است که Client به آن وابسته است.

NullObject : این همان کلاس Null Object است که به عنوان یک وابستگی توسط Client مورد استفاده قرار می‌گیرد. این کلاس هیچ عملکرد مشخصی را ندارد ولی باید تمام اعضای کلاس پایه، یعنی DependencyBase را پیاده سازی کند.

مثال زیر کدهای اصلی پیاده سازی الگوی طراحی Null Object را نشان خواهد داد که با زبان سی شارپ نوشته شده‌است. کلاس Client، وابستگی‌های خود را از طریق سازنده دریافت خواهد کرد که به آن Constructor injection گفته می‌شود. همانطور که می‌بینید در کلاس NullObject، تنها متد Operation بازنویسی شده است و داخل آن هیچ عملکرد خاصی پیاده سازی نشده است؛ زیر تنها به وجود آن نیاز است و نه عملکرد داخلی آن.

public class Client
{
    DependencyBase _dependency;
 
    public void Client(DependencyBase dependency)
    {
        _dependency = dependency;
    }
 
    public void DoSomething()
    {
        _dependency.Operation();
    }
}
 
 
public abstract class DependencyBase
{
    public abstract void Operation();
}
 
 
public class Dependency : DependencyBase
{
    public override void Operation()
    {
        Console.WriteLine("Dependency.Operation() executed");
    }
}
 
 
public class NullObject : DependencyBase
{
    public override void Operation() { }
}


یک نمونه واقعی از الگوی طراحی Null Object

در این بخش قصد داریم مثالی از الگوی استراتژی را ارائه دهیم که در یکی از استراتژی‌هایش از کلاس Null Object استفاده خواهد کرد. در این مثال کلاسی وجود دارد به نام StatusMonitor که پس از انجام کارهایی، وضعیت انجام آن را اعلام می‌کند. ۳ نوع استراتژی برای اعلام وضعیت انجام کارها متصور است که بسته به موقعیت‌های مختلف، یکی از آنها انتخاب خواهد شد. استراتژی‌های اعلام وضعیت شامل ارسال ایمیل، ارسال وضعیت به یک وب سرویس و یا اصلا اعلام نکردن وضعیت هستند. زمانیکه قصد داریم هیچگونه وضعیتی اعلام نشود، از نمونه‌ای از کلاس Null Object استفاده خواهد شد که در این مثال کلاس NullStatusReporter این وابستگی را تامین می‌کند. همه کلاس‌های استراتژی که بیان شد تنها شامل یک متد هستند که از آن برای گزارش پیام وضعیت استفاده خواهیم کرد.

کلاس‌های EmailStatusReporter و WebServiceStstusReporter در صورتیکه بتوانند به درستی پیام‌ها را گزارش دهند، مقدار true را برگشت خواهند داد و در غیر اینصورت مقدار false برگشت داده می‌شود. اما کلاس Null Object هیچ کاری را انجام نمی‌دهد و چیزی را گزارش نمی‌دهد و تنها مقدار true را برگشت خواهد داد. اینکه این کلاس چه مقداری را برگشت دهد، قراردادی است که بین Client و Dependency انجام می‌گیرد. به این نکته هم توجه بفرمایید که کلاس NullStatusReporter به صورت Singleton پیاده سازی شده است.

public class StatusMonitor
{
    StatusReporterBase _reporter;
 
    public StatusMonitor(StatusReporterBase reporter)
    {
        _reporter = reporter;
    }
 
    public void CheckStatus()
    {
        // Do something to check status
        if (!_reporter.Report("Everything's OK"))
        {
            Console.WriteLine("Failed to report status.");
        }
    }
}
 
 
public abstract class StatusReporterBase
{
    public abstract bool Report(string message);
}
 
 
public class EmailStatusReporter : StatusReporterBase
{
    public override bool Report(string message)
    {
        try
        {
            Console.WriteLine("Emailed '{0}'.", message);
            return true;
        }
        catch
        {
            return true;
            throw;
        }
    }
}
 
 
public class WebServiceStatusReporter : StatusReporterBase
{
    public override bool Report(string message)
    {
        try
        {
            Console.WriteLine("Sent '{0}' to web service.", message);
            return true;
        }
        catch
        {
            return true;
            throw;
        }
    }
}
 
 
public class NullStatusReporter : StatusReporterBase
{
    private static NullStatusReporter _instance;
    private static object _lock = new object();
 
    private NullStatusReporter() { }
 
    public static NullStatusReporter GetReporter()
    {
        lock (_lock)
        {
            if (_instance == null) _instance = new NullStatusReporter();
        }
 
        return _instance;
    }
 
    public override bool Report(string message)
    {
        return true;
    }
}


تست کلاس Null Object

برای تست کلاس StatusMonitor باید یکی از انواع استرتژی‌ها را برایش تعیین و آن را به سازنده کلاس تزریق کرد و با آن استراتژی، کلاس را تست نمود. در کد زیر از استراتژی NullObject استفاده شده‌است. پس یک نمونه‌ی آن ساخته شده و از طریق سازنده به کلاس StatusMonitor فرستاده می‌شود. سپس متد CheckStatus فراخوانی می‌گردد. اما این متد کاری را انجام نمی‌دهد و تنها مقدار true  برگشت داده می‌شود. بررسی روش‌های دیگر را به خودتان واگذار می‌کنم.

StatusReporterBase reporter = NullStatusReporter.GetReporter();
StatusMonitor monitor = new StatusMonitor(reporter);
monitor.CheckStatus();


پروژه‌ها
فروشگاه IrisStore
پروژه IrisStore، یک سیستم فروشگاهی متن باز برای راه اندازی فروشگاه‌های اینترنتی کوچک است که سورس آن را می‌توانید از آدرس زیر دریافت کنید و برای اجرای آن نیاز به VS 2015 دارید: 

https://github.com/MehdiSaeedifar/IrisStore 

همچنین نمونه‌ی آنلاین آن‌را می‌توانید در فروشگاه آیریس مشاهده کنید.


در ادامه برخی از قابلیت‌های این سیستم را مشاهده می‌کنید: 
 

جست و جو با قابلیت دسته بندی نتایج
 

به هنگام جست و جو، لیستی از موارد پیشنهادی به صورت دسته بندی شده نمایش داده می‌شود. 



جست و جوی پیشرفته کالا‌ها 

جست و جو بر اساس قیمت، گروه، کلمات کلیدی و مرتب سازی نتایج انجام می‌گیرد. همچنین نتایج جست و جو بدون رفرش شدن صفحه و به صورت AJAX ای به همراه تغییر URL صفحه صورت می‌گیرد. 



نمایش نمودار تغییرات قیمت 
 
امکان نمایش نمودار تغییرات قیمت کالا در بازه‌ی زمانی نیز پیش بینی شده است. 


ویرایش اطلاعات به صورت inline 
 
امکان ویرایش قیمت و تاریخ به صورت inline وجود دارد.



 

مدیریت تصاویر کالا

  
در این قسمت امکان آپلود همزمان چندین فایل به همراه پیش نمایش آن‌ها وجود دارد. همچنین امکان کشیدن و رها کردن برای تغییر ترتیب چیدمان عکس‌ها نیز مهیا است.( تصویر اول به عنوان کاور کالا در نظر گرفته می‌شود.)


 

قابلیت‌های دیگر:

  
- مدیریت تصاویر اسلایدشو و تغییر ترتیب آن‌ها از طریق کشیدن و رها کردن (drag & drop)
- تعریف برگه و تغییر ترتیب نمایش آن‌ها از طریق کشیدن و رها کردن
- امکان ارسال پست
- تعریف دسته بندی
- مدیریت کاربران
- تعریف تنظیمات سایت
- نمایش کالا و پست‌های مشابه


تصویر پنل مدیریت


تصویر صفحه‌ی اصلی:



همچنین به راحتی می‌توان با طراحی قالب جدیدی، از این سیستم برای کاری غیر از فروشگاه اینترنتی استفاده کرد؛ سایت‌های زیر نمونه‌های آنلاین دیگری از این سیستم هستند:

http://www.petrapars.ir 
http://www.ava-tarh.ir 

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

کتابخانه‌ها و فریم ورک‌های سمت سرور:

 فناوری یا کتابخانه   توضیحات
مقالات مرتبط
 ASP.NET MVC 5.x 
 فریم ورک و موتور اصلی سایت
-ASP.NET MVC 
-How to handle repeating form fields in ASP MVC 
-How to dynamically (via AJAX) add new items to a bound list model, in ASP MVC.NET  
 Entity Framework 6.x 
 فریم ورک دسترسی به داده
-Entity framework code-first 
-Update One-to-Many Entity using DBContext
-مدیریت اطلاعات وابسته به زمان در بانک‌های اطلاعاتی رابطه‌ای 
EFSecondLevelCache 
کش سطح دوم EF 6
 -بازنویسی سطح دوم کش برای Entity framework 6
 AutoMapper 
 نگاشت اطلاعات یک شی به شی دیگر به صورت خودکار  دوره AutoMapper 
خودکارسازی فرآیند نگاشت اشیاء در AutoMapper
 StructureMap 
 تزریق وابستگی‌ها 
-EF Code First #12
 MvcCheckBoxList 
 اضافه کردن CheckBoxList  به HtmlHelper

 DNTScheduler 
 برای انجام کارهای زمان بندی شده
-انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
 Lucene.Net 
 موتور جستجوی سایت  -جستجوی سریع و پیشرفته با لوسین Lucene.net
 AspNet.Identity 
 سیستم مدیریت کاربران
-اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
 ELMAH.MVC 
 کتابخانه ثبت وقایع و خطا‌های سیستم  -معرفی ELMAH
 PagedList 
 نمایش اطلاعات به صورت صفحه بندی شده

PersianDateTime 
جایگزینی است برای System.DateTime برای تاریخ‌های شمسی 
-PersianDateTime جایگزینی برای System.DateTime
T4MVC 
تعاریف Strongly typed مسیرها
-T4MVC : یکی از الزامات مدیریت پروژه‌های ASP.NET MVC
Dynamic LINQ 
نوشتن کوئری‌های LINQ به صورت رشته ای
-انتخاب پویای فیلد‌ها در LINQ 
-فعال سازی و پردازش جستجوی پویای jqGrid در ASP.NET MVC

کتابخانه‌های جاوا اسکریپتی سمت کلاینت:

 فناوری یا کتابخانه
  توضیحات     مقالات مرتبط
 jQuery  کتاب خانه‌ی پایه جاوا اسکرپتی سایت
 -آموزش (jQuery) جی کوئری 
-آموزش JQuery Plugin و مباحث پیشرفته جی کوئری

 jQuery UI  ویجت‌های رابط کاربری
نمایش رکوردها به ترتیب اولویت به کمک jQuery UI sortable در ASP.NET MVC 
jQuery UI Sortable 
-Categorized search result with jQuery UI Autocomplete 
jQuery UI Slider 
-rtl jQuery UI Slider 
-jquery UI Sortable with table and tr width
jQuery Validation اعتبار سنجی سمت کلاینت
-مشکل اعتبار سنجی jQuery validator در Bootstrap tabs 
-نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
toastr نمایش پیام و اطلاع رسانی

PersianDatePicker یک DatePicker شمسی کم حجم
-PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
CKEDITOR ادیتور متن
-استفاده از ادیتور CKEditor در صفحات ASP.NET 
-یکپارچه سازی CKEditor با Lightbox 
Roxy Fileman مدیریت فایل ها  -افزونه مدیریت فایل‌های رایگان Roxy FileMan برای TinyMce و CkEditor  
Magnific Popup نمایش عکس‌ها به صورت پاپ آپ

Select2 تغییر شکل drop down list‌ها برای انتخاب گزینه‌ها

jqGrid v4.6 نمایش اطلاعات در قالب جدول
آموزش jqGrid
Bootstrap Star Rating امتیاز دهی ستاره ای
-پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
jQuery File Upload Plugin آپلود فایل به صورت AJAX ای

HIGHCHARTS نمایش نمودار

jQuery Number Plugin برای فرمت کردن اعداد

X-editable ویرایش اطلاعات به صورت inline
-قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت اول
bootstrap-confirmation نمایش فرم تایید در قالب popover

PathJS برای تغییر URL صفحه برای اعمال Ajax ای
-پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC

فریمورک‌های CSS:

فناوری یا کتابخانه 
 توضیحات
 مقالات مرتبط
 Bootstrap 3.x
 فریم ورک پایه ای css سایت
 - Bootstrap 3 RTL Theme 
Twitter Bootstrap 
-سازگارسازی کلاس‌های اعتبارسنجی Twitter Bootstrap 3 با فرم‌های ASP.NET MVC
-ساخت قالب‌های نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC 
-نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
 AdminLTE
 قالب مدیریت سایت
 - نسخه راستچین شده AdminLTE 2.2.1
Animate.css انیمیشن‌های css3 سایت

Font Awesome پک آیکون‌های برداری

Awesome Bootstrap Checkbox زیبا سازی چک باکس ها

فونت فارسی وزیر قلم فارسی
 

مطالب
طراحی و پیاده سازی DomainEvents

شرایطی را در نظر بگیرید که نیاز است از تغییرات یک Entity در سیستم آگاه شویم. برای مثلا در زمان ثبت سفارش جدید در فروشگاه، ایمیلی به مدیر فروشگاه ارسال شود، یک Business Rule نیز چک شود و همچنین بنابر نیاز مشتری، تعداد آنها روز به روز ممکن است افزایش یابد و چه بسا در اعمال این Ruleها، موجودیت‌های مختلفی درگیر باشند. در این صورت است که خواسته یا ناخواسته اتصال بین کلاس‌ها خیلی افزایش خواهد یافت. یکی از راه حل‌های رهایی از این پیچیدگی و اتصال بالا، استفاده از Event می‌باشد.

هدف طراحی و پیاده سازی زیرساختی برای استفاده از DomainEventها می‌باشد. کدهای کامل این مطلب را می‌توانید از اینجا دریافت کنید. 


Domain Event چیست؟

چیزی که در یک Domain خاصی رخ داده است و هدف از آن آگاه کردن سایر بخش‌های آن Domain می‌باشد تا بتوانند واکنش مناسبی را نشان دهند. با بهره گیری از این نوع رویدادها، می‌توان Separation Of Concerns خوبی را بین کلاس‌های موجود در آن Domain اعمال کرد و به طراحی ای با Coupling پایین رسید. این رویداد‌ها عموما داخل پروسه Raise می‌شوند.

برای اطلاعات بیشتر در این زمینه پیشنهاد میکنم این مطلب را مطالعه کنید.
کار را با معرفی واسط IDomainEvent آغاز می‌کنیم.
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEvent : ITransientDependency
    {
    }
}
در کد بالا، واسط ITransientDepedency برای اعمال طول عمر وهله‌های ساخته شده توسط StructureMap در نظر گرفته شده است. برای علامت گذاری DomainEventها، از واسط بالا استفاده خواهیم کرد. 

واسط IDomainEventHandler
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEventHandler<in T> : ITransientDependency
        where T : IDomainEvent
    {
        bool IsAdvisable { get; }
        void Handle(T domainEvent);
    }
}
از کلاس‌های پیاده ساز واسط بالا، می‌توان برای مدیریت رویداد خاصی استفاده کرد. برای علامت گذاری DomainEventHandlerها نیز از این واسط استفاده میشود. 
خصوصیت IsAdvisable: اگر مقدار آن true باشد، در این صورت در زمان صدور استثنایی در روند اجرای متد Handle آن از این استثناء چشم پوشی شده و مابقی هندلرها فراخوانی خواهند شد.

پیاده سازی Engine مربوط به Raise کردن رویدادها
روند کار به این شکل است:
  1. متد Raise مربوط به Engine برای رویداد خاصی فراخوانی می‌شود.
  2. با استفاده از یک IOC Container، تمام هندلرهای مربوط به رویداد جمع آوری می‌شود.
  3. متد Handle مربوط به تک تک هندلرها، فراخوانی خواهد شد.
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEventEngine : ISingletonDependency
    {
        void Raise<T>(T domainEvent) where T : IDomainEvent;
    }
}

namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public class DomainEventEngine : IDomainEventEngine
    {
        private readonly IContainer _container;

        public DomainEventEngine(IContainer container)
        {
            _container = container;
        }

        public void Raise<T>(T domainEvent) where T : IDomainEvent
        {
            foreach (var handler in _container.GetAllInstances<IDomainEventHandler<T>>())
                try
                {
                    handler.Handle(domainEvent);
                }
                catch (Exception)
                {
                    if (domainEvent.IsAdvisable && handler.IsAdvisable)
                        throw;
                }
        }
    }
}
طول عمر این Engine به صورت Singleton در نظر گرفته شده است. همانطور که گفته شد، در صورت صدور استثناء، در صورت IsAdvisable بودن خود رویداد، خصوصیت IsAdvisable هندلر آن بررسی خواهد شد.
شاید بهتر باشد یکسری رویداد پیش فرض هم در زیرساخت پروژه خود داشته باشیم. برای مثال رویدادهای مربوط به Entityها که در زیر آنها را مشاهده می‌کنید:
namespace DomainEventsSample.Framework.Domain.Events
{
    public abstract class EntityDomainEvent<TEntity> : IDomainEvent
        where TEntity : Entity
    {
        protected EntityDomainEvent(TEntity entity)
        {
            Entity = entity;
        }

        public TEntity Entity { get; }
    }
}

کلاس بالا به عنوان کلاس پایه یکسری رویداد مشترک مابین Entity‌های سیستم در نظر گرفته شده است.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityCreatingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityCreatingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityCreatedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityCreatedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ایجاد یک Entity می‌باشند.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityEditingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityEditingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityEditedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityEditedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ویرایش یک Entity می‌باشند. 

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityDeletingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityDeletingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityDeletedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityDeletedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از حذف یک Entity می‌باشند.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntitySavingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntitySavingEvent(TEntity entity) : base(entity)
        {
        }
    }
}

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntitySavedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntitySavedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ذخیره (ایجاد و ویرایش) یک Entity می‌باشند.

نکته: برای اسکن کردن تمام هندلرها لازم است کد زیر را به تنظیمات StructureMap اضافه کنید:

Scan(scan =>
{
    scan.ConnectImplementationsToTypesClosing(typeof(IDomainEventHandler<>));
});

مثال: برای مثال این بار برای آگاه سازی کاربران به صورت بلادرنگ از اضافه شدن یک کالا، می‌توان با هندل کردن رویداد مربوط به ایجاد کالا، به شکل زیر عمل کرد:
public class ProductCreatedEventHandler : IDomainEventHandler<EntityCreatedEvent<Product>>
{
    public bool IsAdvisable => false;

    public void Handle(EntityCreatedEvent<Product> domainEvent)
    {
        //todo: notify users
    }
}

در متد Create مربوط به ProductApplicationService و بعد از عملیات ذخیره سازی به شکل زیر می‌بایست عمل کرد:

public class ProductApplicationService : IProductApplicationService
{
    private readonly IDomainEventEngine _eventEngine;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;

    public ProductApplicationService(IDomainEventEngine eventEngine,IMapper mapper,IUnitOfWork unitOfWork)
    {
        _eventEngine = eventEngine;
        _mapper=mapper;
        _unitOfWork=unitOfWork;
    }

    [Transactional]
    public void Create(ProductCreateViewModel model)
    {
         var entity=_mapper.Map<Product>(model);
        _unitOfWork.Set<Product>().Add(entity);
        _unitOfWork.SaveChanges();
        _eventEngine.Raise(new EntityCreatedEvent<Product>(entity));
    }
}

البته بهتر است برای Raise کردن این نوع رویدادها از مکانیزم Hook استفاده کرد و در زمان ذخیره سازی و فراخوانی متد SaveChange، این عملیات به صورت خودکار صورت گیرند.

در مقاله بعدی با استفاده از Hookها این عملیات را انجام خواهیم داد. 


کدهای این قسمت را می‌توانید از اینجا دریافت کنید.
نظرات مطالب
نمایش پیام هشدار در Blazor با استفاده از کامپوننت Alert بوت استرپ ۵
روش دوم پیاده سازی این مثال: ارسال یک کامپوننت محصور کننده‌ی سراسری، به صورت پارامتر آبشاری، به تمام زیر کامپوننت‌ها

با استفاده از پارامترهای آبشاری می‌توان شیءای را در اختیار تمام کامپوننت‌های قرار گرفته شده‌ی در سلسله مراتب آن‌ها قرار داد. برای مثال اگر در فایل Client\Shared\MainLayout.razor، جائیکه سایر کامپوننت‌ها قرار است رندر شوند را توسط یک کامپوننت سطح بالا محصور کنیم:
<Alert>
  @Body
</Alert>
در این حالت هر کامپوننتی که بجای Body درج شود، می‌تواند به پارامترهای آبشاری Alert دسترسی پیدا کند؛ یعنی تمام کامپوننت‌های نمایشی برنامه. یا حتی می‌توان این Alert را در فایل Client\App.razor نیز در بالاترین سطح قرار داد و کل Router را توسط آن محصور کرد.
بنابراین طراحی ساده‌ی کامپوننت Alert ای (Client\Shared\Alert.razor) که تامین کننده‌ی یک پارامتر آبشاری سراسری است، به صورت زیر می‌تواند باشد:
<CascadingValue Value=this>
    @if(IsVisible)
    {
        <div class="alert @Css" role="alert">
            @Message
            <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="HideAlert">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    }
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool IsVisible;
    private string Message;
    private string Css = "alert-primary";

    public void ShowAlert(string message, AlertType alertType)
    {
        IsVisible = true;
        Message = message;

        Css = alertType switch
        {
            AlertType.Success => "alert-success",
            AlertType.Info => "alert-primary",
            AlertType.Danger => "alert-danger",
            AlertType.Warning => "alert-warning",
            _ => "alert-primary"
        };

        StateHasChanged();
    }

    public void HideAlert()
    {
        IsVisible = false;
    }
}
که البته در همان پوشه به همراه فایل Client\Shared\AlertType.cs نیز هست:
namespace BlazorWasmAlert.Client.Shared
{
    public enum AlertType
    {
        Success,
        Info,
        Danger,
        Warning
    }
}
در کامپوننت ویژه‌ی Alert، دو قابلیت استاندارد Blazor بکار گرفته شده‌اند:
الف) وجود یک CascadingValue که اینبار Value آن به خود کامپوننت اشاره می‌کند (Value=this). یعنی پارامتر آبشاری که در اختیار سایر کامپوننت‌های محصور شده‌ی توسط آن ارسال می‌شود، دقیقا وهله‌ای از کامپوننت Alert است که توسط آن می‌توان برای مثال، متد عمومی ShowAlert آن‌را فراخوانی کرد:
<CascadingValue Value=this>
ب) چون کامپوننت Alert قرار است کامپوننت‌هایی را که بجای body@ درج می‌شوند را نمایش دهد، اینکار را توسط یک RenderFragment انجام داده‌است.


پس از درج این کامپوننت در فایل layout، روش استفاده‌ی از آن برای مثال در کامپوننت Index به صورت زیر است:
@page "/"

<h1>Hello, world!</h1>

<button class="btn btn-primary" @onclick="ShowAlert">Show Alert!</button>

@code
{
    [CascadingParameter]
    public Alert Alert { get; set; }

    private void ShowAlert()
    {
        Alert.ShowAlert("This is a test!", AlertType.Info);
    }
}
در اینجا پارامتر آبشاری که دریافت می‌شود، دقیقا به کلاس و شیء Alert (وهله‌ای از کامپوننت Alert) اشاره می‌کند. به همین جهت می‌توان متد عمومی ShowAlert آن‌را در اینجا فراخوانی کرد.


پ.ن.
در طراحی Blazor، از طراحی React الهام گرفته شده‌است و CascadingValue آن دقیقا معادل Context API جدید React است.
نظرات مطالب
راهبری در Silverlight به کمک الگوی MVVM
یک روش : برای پاس دادن یک وهله از یک کلاس به کلاسی دیگر در Silverlight یا WPF (مثلا در حین Navigation) می‌تونید از امکانات کلاس Messenger مجموعه‌ی MVVM Light toolkit استفاده کنید.
توضیحات مرتبط با هر کدام در جزوه MVVM (برای WPF) یا کتاب Silverlight منتشر شده موجود است.

مثال فوق هم به همین ترتیب یک رشته را از یک کلاس به کلاس دیگر پاس داده است.
مطالب
انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم

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

چندین کتابخانه و کلاس جهت مدیریت Coroutines در دات نت تهیه شده که لیست آن‌ها به شرح زیر است:
1) Using C# 2.0 iterators to simplify writing asynchronous code
2) Wintellect's Jeffrey Richter's PowerThreading Library
3) Rob Eisenberg's Build your own MVVM Framework codes

و ...

مورد سوم که توسط خالق اصلی کتابخانه‌ی Caliburn (یکی از فریم ورک‌های مشهور MVVM برای WPF و Silverlight) در کنفرانس MIX 2010 ارائه شد، این روزها در وبلاگ‌های مرتبط بیشتر مورد توجه قرار گرفته و تقریبا به یک روش استاندارد تبدیل شده است. این روش از یک اینترفیس و یک کلاس به شرح زیر تشکیل می‌شود:

using System;

namespace SLAsyncTest.Helper
{
public interface IResult
{
void Execute();
event EventHandler Completed;
}
}

using System;
using System.Collections.Generic;

namespace SLAsyncTest.Helper
{
public class ResultEnumerator
{
private readonly IEnumerator<IResult> _enumerator;

public ResultEnumerator(IEnumerable<IResult> children)
{
_enumerator = children.GetEnumerator();
}

public void Enumerate()
{
childCompleted(null, EventArgs.Empty);
}

private void childCompleted(object sender, EventArgs args)
{
var previous = sender as IResult;

if (previous != null)
previous.Completed -= childCompleted;

if (!_enumerator.MoveNext())
return;

var next = _enumerator.Current;
next.Completed += childCompleted;
next.Execute();
}
}
}

توضیحات:
مطابق توضیحات قسمت قبل، برای مدیریت اعمال همزمان به شکلی پی در پی، نیاز است تا یک IEnumerable را به همراه yield return در پایان هر مرحله از کار ایجاد کنیم. در اینجا این IEnumerable را از نوع اینترفیس IResult تعریف خواهیم کرد. متد Execute آن شامل کدهای عملیات Async خواهند شد و پس از پایان کار رخداد Completed صدا زده می‌شود. به این صورت کلاس ResultEnumerator به سادگی می‌تواند یکی پس از دیگری اعمال Async مورد نظر ما را به صورت متوالی فراخوانی نمائید. با هر بار فراخوانی رخداد Completed، متد MoveNext صدا زده شده و یک مرحله به جلو خواهیم رفت.
برای مثال کدهای ساده WCF Service زیر را در نظر بگیرید.

using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Threading;

namespace SLAsyncTest.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class TestService
{
[OperationContract]
public int GetNumber(int number)
{
Thread.Sleep(2000);//Simulating a log running operation
return number * 2;
}
}
}

قصد داریم در طی دو مرحله متوالی این WCF Service را در یک برنامه‌ی Silverlight فراخوانی کنیم. کدهای قسمت فراخوانی این سرویس بر اساس پیاده سازی اینترفیس IResult به صورت زیر درخواهند آمد:

using System;
using SLAsyncTest.Helper;

namespace SLAsyncTest.Model
{
public class GetNumber : IResult
{
public int Result { set; get; }
public bool HasError { set; get; }

private int _num;
public GetNumber(int num)
{
_num = num;
}

#region IResult Members
public void Execute()
{
var srv = new TestServiceReference.TestServiceClient();
srv.GetNumberCompleted += (sender, e) =>
{
if (e.Error == null)
Result = e.Result;
else
HasError = true;

Completed(this, EventArgs.Empty); //run the next IResult
};
srv.GetNumberAsync(_num);
}

public event EventHandler Completed;
#endregion
}
}
در متد Execute کار فراخوانی غیرهمزمان WCF Service به صورتی متداول انجام شده و در پایان متد Completed صدا زده می‌شود. همانطور که توضیح داده شد، این فراخوانی در کلاس ResultEnumerator یاد شده مورد استفاده قرار می‌گیرد.
اکنون قسمت‌های اصلی کدهای View Model برنامه به شکل زیر خواهند بود:

private void doFetch(object obj)
{
new ResultEnumerator(executeAsyncOps()).Enumerate();
}

private IEnumerable<IResult> executeAsyncOps()
{
FinalResult = 0;
IsBusy = true; //Show BusyIndicator

//Sequential Async Operations
var asyncOp1 = new GetNumber(10);
yield return asyncOp1;

//using the result of the previous step
if(asyncOp1.HasError)
{
IsBusy = false; //Hide BusyIndicator
yield break;
}

var asyncOp2 = new GetNumber(asyncOp1.Result);
yield return asyncOp2;

FinalResult = asyncOp2.Result; //Bind it to the UI

IsBusy = false; //Hide BusyIndicator
}
در اینجا یک IEnumerable از نوع IResult تعریف شده است و در طی دو مرحله‌ی متوالی اما غیرهمزمان کار دریافت اطلاعات از WCF Service صورت می‌گیرد. ابتدا عدد 10 به WCF Service ارسال می‌شود و خروجی 20 خواهد بود. سپس این عدد در مرحله‌ی بعد مجددا به WCF Service ارسال گردیده و حاصل نهایی که عدد 40 می‌باشد در اختیار سیستم Binding قرار خواهد گرفت.
اگر از این روش استفاده نمی‌شد ممکن بود به این جواب برسیم یا خیر. ممکن بود مرحله‌ی دوم ابتدا شروع شود و سپس مرحله‌ی اول رخ دهد. اما با کمک Iterators و yield return به همراه کلاس ResultEnumerator موفق شدیم تا عملیات دوم همزمان را در حالت تعلیق قرار داده و پس از پایان اولین عملیات غیر همزمان، مرحله‌ی بعدی فراخوانی را بر اساس مقدار حاصل شده از WCF Service آغاز کنیم.
این روش برای برنامه‌ نویس‌ها آشناتر است و همان سیستم فراخوانی A->B->C را تداعی می‌کند اما کلیه اعمال غیرهمزمان هستند و ترد اصلی برنامه قفل نخواهد شد.

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

مطالب
تفاوت بین Interface و کلاس Abstract در چیست؟
یکی از سوالات مصاحبه‌ای که اکثر مواقع پرسیده میشود، تفاوت Interface و  Abstract class می‌باشد؛ امیدوارم این مقاله برای شما مفید باشد.

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

مزایای  Interface ‌ها چیست ؟

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

 

Abstract Class چیست ؟

کلاس Abstract، یکی از ابزارهای مهم OOP می‌باشد که نمی‌توان از آنها نمونه‌ای ساخت. به عبارتی دیگر نمی‌توانیم متغیری از کلاس Abstract تعریف کنیم. یک کلاس Abstract  شبیه Interface میباشد ولی با دیدی وسیعتر. این کلاسها می‌تواند دارای متدهای Abstract باشند که شبیه Interface فقط اعلام میشوند و باید در کلاسهای مشتق شده بازنویسی شوند. البته میتوان در این کلاسها متدهایی داشت که Abstract نیستند و احتیاجی به پیاده سازی آنها در کلاسهای مشتق شده ندارند.

باید توجه داشت که تنها متدهایی از کلاس abstract الزام به پیاده سازی دارند که صریحا کلمه‌ی abstract در تعریف آن متد ذکر شده باشد.
در واقع همین متد‌ها هم الزامی به پیاده سازی ندارند. یعنی می‌شود در subclass هم به صورت abstract ذکر شوند. البته به شرطی که subclass هم به صورت abstract تعریف شده باشد.
در ضمن کلاس abstract میتواند متد‌های ساده یا غیر abstract هم داشته باشد. همانطور که میدانید متد‌های غیر abstract باید بدنه داشته باشند و نیازی به پیاده سازی ندارند.
پس کلاس abstract هم میتواند متدهایی داشته باشد که باید پیاده سازی شوند و هم متدهایی داشته باشد که لازم نباشد پیاده سازی شوند.

با توجه به تعاریف ذکر شده کلاس Abstract  حالتی بین کلاسهای معمولی و Interface‌ها میباشد و کلاسی میباشد که غیر قطعی و ناتمام است که باید در سطح فرزندانش تکمیل شود .

 

 مزایای کلاسهای  Abstract چیست ؟

یکی از مزیت‌های کلاس Abstract  فراهم نمودن کلاسی پایه برای دیگر کلاسهای مشتق شده است؛ با این توضیح که متدهای آن می‌توانند کد نویسی شده باشند یا خیر. از طرفی پیاده سازی تمام متدهای Abstract در کلاس مشتق شده اجباری نیست (برخلاف Interface).

تعریف سطوح دسترسی برای متدها و خصوصیت‌ها مانند کلاسهای معمولی نیز یکی دیگر از مزیت‌های این کلاس‌ها است.

  

 تفاوت بین کلاسهای  Abstract  و  Interface

1- یک کلاس معمولی تنها می‌تواند از یک کلاس Abstract ارث بری کند ولی همان کلاس میتواند از چندین Interface ارث ببرد.

2- یک Interface  فقط میتواند اعلان متدها و خصوصیتها را داشته باشد؛ اما یک کلاس Abstract  علاوه بر آنها میتوانید متدها و خصوصیتهایی با کدهای کامل داشته باشد.

3- عناصر موجود در کلاس Abstract میتوانند مانند یک کلاس معمولی دارای سطح دسترسی باشند؛ ولی Interface‌ها فاقد این امکان هستند.

4- وقتی شما متدی را به کلاس Abstract اضافه می‌کنید، به طور خودکار به همه زیر کلاسها اعمال می‌شود؛ اما در Interface اگر متدی اضافه کنید باید در تمام زیر کلاسها آن را اعمال کنید .

5- کلاس‌های Abstract مانند کلاسهای معمولی می‌توانند دارای فیلد و عناصر دیگری (مثل ثابت‌ها) باشند؛ در حالیکه یک Interface فاقد این امکان می‌باشد. همچنین کلاس abstract میتواند شامل سازنده باشد، اما اینترفیس نمیتواند.

6- Abstract  یکی از انواع کلاس است؛ ولی Interface کلاس نیست .

7- اینترفیس تنها میتواند از اینترفیس ارث بری کند اما کلاس abstract میتواند از اینترفیس، کلاس Abstract و یا سایر کلاس‌ها ارث بری کند. 

  

چه زمانی از  Interface ‌ها و یا کلاسهای  Abstract  استفاده کنیم؟

- با توجه به توضیحات ذکر شده  مواقعی که نیاز به وراثت چند گانه داریم، باید از Interface استفاده کنیم؛ به دلیل اینکه این امکان در کلاس‌های Abstract  وجود ندارد.

- زمانی که بخواهیم تمام متدهای معرفی شده در کلاس پایه به طور کامل در کلاس مشتق شده پیاده شوند باید از Interface استفاده کنیم.

- وقتی در پروژه‌های بزرگ با تغییرات زیادی مواجه هستیم، استفاده از کلاس Abstract  توصیه می‌شود؛ چون با تغییر آن به طور خودکار تغییرات در کلاسهای مشتق شده اعمال می‌شوند.

- با توجه به اینکه به غیر از اعلان متدها و خصوصیت‌ها امکان تعریف عناصر دیگری در Interfaceها وجود ندارد، در صورتیکه ملزم به استفاده  از این عناصر باشیم، استفاده از کلاسهای Abstract  ضروری می‌باشد.

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

- به طور کلی یک Interface چارچوب و قابلیتهای یک کلاس را مشخص میکند و یک قرارداد است؛ ولی کلاس Abstract نوع کلاس را معین می‌کند. این تفاوت کمک بسیاری برای تشخیص زمان استفاده از این دو را به برنامه نویسان میدهد.

مطالب
آموزش LINQ بخش ششم - عملگرهای پرس و جو قسمت اول
عملگرهای استاندارد پرس و جو

در یک طبقه بندی کلی، عملگرهای پرس و جو بر اساس ورودی و خروجی آنها به سه دسته تقسیم می‌شوند:
1- نتیجه‌ی توالی ورودی، بصورت یک توالی، به خروجی ارسال می‌شود.
2- نتیجه‌ی توالی ورودی، بصورت یک عنصر یکتا و واحد به خروجی ارسال می‌شود.
3- اثری از ورودی در توالی خروجی وجود ندارد (این عملگرها عناصر خودشان را تولید می‌کنند).

دسته‌ی آخر شاید کمی عجیب به نطر برسد. این عملگرها هیچ توالی ورودی را دریافت نمی‌کنند. مثلا می‌توان از طریق این عملگر‌ها، یک توالی از اعداد صحیح را تولید کرد.
تقسیم بندی عملگرهای پرس و جو بر اساس عملکرد به صورت زیر می‌باشد : 
  • محدود کننده (Restriction)
where
  • بازتابی (Projection)
Select,SelectMany 
  • جداکننده (Partitioning)
Take,Skip,TakeWhile,SkipWhile 
  • مرتب سازی (Ordering)
OrderBy,OrderByDescending,ThenBy,ThenByDescending,Reverse 
  • گروه بندی (Grouping)
GroupBy 
  • مجموعه (Set)
Concat,Union,Intersect,Except 
  • تبدیل (Conversion)
ToArray,ToList,ToDictionary,ToLookup,OfType,Cast 
  • عنصر(Element)
First,FirstOrDefault,Last,LastOrDefalt,Single,SingleOrDefault 
  • عنصر در (ElementAt)
ElementAtOrDefault,DefaultIfEmpty 
  • تولید (Generation)
Empty,Range,Report 
  • کمی (Quantifier)
Any,All,Contains,SequenceEqual 
  • مجموعه (Aggregate)
Count,LongCount,Sum,Min,Max,Average,Aggregate 
  • اتصال (Join)
Join,GroupJoin,Zip 

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

عملگرهای محدود کننده (Restriction Operators)
این عملگرها یک توالی ورودی را دریافت و یک توالی محدود شده یا به بیان دیگر فیلتر شده را تولید می‌کنند. عناصر توالی خروجی، عناصری هستند که با فیلتر اعمال شده مطابقت دارند.
Where
این عملگر، عناصری را به خروجی ارسال می‌کند که با گزاره‌ی (Predicate) تعریف شده مطابقت داشته باشند.
نکته : گزاره (Predicate) تابعی است که اگر شرط آن تامین شود، مقدار true و در غیر اینصورت مقدار false را باز می‌گرداند.
مثال : 
 Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Where(x => x.Calories >= 200);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
در کد فوق از عملگر where استفاده شده است. گزاره‌ی (x=>x.Calories>=200) به ازای هر غذایی که کالری آن مساوی یا بزرگتر از 200 باشد، مقدار true را باز می‌گرداند.
خروجی کد بالا:
 Sugar
Butter
عملگر where امضای دیگری دارد که اندیس عنصر ورودی توالی را نیز می‌پذیرد. در مثال قبل، اندیس Sugar برابر 0 و اندیس Butter برابر 4 است. پرس و جوی زیر خروجی مشابه مثال قبل را تولید می‌کند.
 IEnumerable<Ingredient> query = ingredients.Where((ingredient, index) => ingredient.Name == "Sugar" || index == 4);
گزاره نوشته شده در این پرس و جو  از نوع <Func<Ingredient,int,bool خواهد بود و پارامتر int، اندیس عنصر در توالی ورودی می‌باشد.

پیاده سازی توسط عبارت‌های پرس و جو
 در روش عبارت‌های پرس و جو، کلمه‌ی کلیدی where به‌همراه یک عبارت منطقی در پرس و جو ظاهر می‌شود:
 IEnumerable<Ingredient> gueryExpression =
from i in ingredients
where i.Calories >= 200
select i;


عملگرهای بازتاب (Projection Operators)

عملگرهای پرس و جوی بازتابی، یک توالی ورودی را دریافت و با تبدیل عناصر آنها، یک توالی خروجی را تولید می‌کنند.

Select
عملگر پرس و جوی select هر عنصر توالی ورودی را به یک عنصر در توالی خروجی تبدیل می‌کند. تعداد عناصر ورودی و خروجی در این حالت یکسان می‌باشند.
پرس و جوی زیر عناصر توالی ورودی Ingredient را به عناصر رشته‌ای در توالی خروجی بازتاب می‌کند. عبارت Lambda تعریف شده، نحوه‌ی بازتاب عناصر را مشخص می‌کند (هر عنصر ingredient به یک عنصر رشته‌ای بازتاب می‌شود):
 IEnumerable<string> query = ingredients.Select(x => x.Name);
  می‌توان توالی خروجی با عناصر صحیح را نیز تولید کرد:  
 IEnumerable<int> query = ingredients.Select(x => x.Name.Length);

در عملیات بازتاب می‌توان یک شیء جدید را در توالی خروجی ایجاد کرد. در کد زیر عناصر Ingredient به یک عنصر جدید از نوع IngredientNameAndLenght بازتاب شده است.
class IngredientNameAndLength
{
    public string Name { get; set; }
    public int Length { get; set; }
    public override string ToString()
    {
      return Name + " " + Length;
    }
}

IEnumerable<IngredientNameAndLength> query = ingredients.Select(x =>
new IngredientNameAndLength
{
   Name = x.Name,
   Length = x.Name.Length
});
پرس و جوی بالا را می‌توان به شکل نوع‌های بی نام نیز بازنویسی کرد. باید دقت شود که نوع بازگشتی این پرس و جو باید از نوع var باشد.
var query = ingredients.Select(x =>
new
{
   Name = x.Name,
   Length = x.Name.Length
});
خروجی کد بالا به شکل زیر است :
{ Name = Sugar, Length = 5 }
{ Name = Egg, Length = 3 }
{ Name = Milk, Length = 4 }
{ Name = Flour, Length = 5 }
{ Name = Butter, Length = 6 }

پیاده سازی توسط عبارت‌های پرس و جو

کلمه‌ی کلیدی select در عبارت‌های پرس و جو، به شکل زیر استفاده می‌شود:
var query = from i in ingredients
select new
{
    Name=i.Name,
    Length=i.Name.Length
};

SelectMany 
برعکس دستور select که به ازای هر عنصر در توالی ورودی، یک عنصر را در توالی خروجی بازتاب می‌کرد، دستور SelectMany ممکن است تعداد عناصر کمتر و یا بیشتری را در توالی خروجی بازتاب کند (انتخاب مقادیر یک مجموعه از مجموعه‌ی دیگر).
عبارت Lambda نوشته شده در عملگر Select، یک مقدار را باز می‌گرداند. اما عبارت Lambda نوشته شده در عملگر SelectMany، یک توالی فرزند (Child Sequence) را ایجاد می‌کند. توالی فرزند ممکن است حاوی تعداد مختلفی از عناصر به ازای هر عنصر در توالی ورودی باشد.
در مثال زیر عبارت Lambda یک توالی فرزند از کاراکتر‌ها ایجاد می‌کند (یک کاراکتر به ازای هر حرف از هر عنصر توالی ورودی). به‌طور مثال عنصر ورودی Sugar، پس از پردازش توسط  عبارت Lambda، یک توالی فرزند با 5 عنصر 's','u','g','e','r' فراهم می‌کند. هر رشته‌ی در توالی Ingredient می‌تواند تعداد حروف متفاوتی داشته باشد. در نتیجه عبارت Lambda، توالی‌های فرزندی با طول‌های مختلف ایجاد می‌کند.
مثال:
string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
IEnumerable<char> query = ingredients.SelectMany(x => x.ToCharArray());
foreach (var item in query)
{
   Console.WriteLine(item);
}
خروجی مثال بالا :
 S
u
g
a
r
E
g
g
M
i
l
k
F
l
o
u
r
B
u
t
t
e
r

پیاده سازی توسط عبارت‌های پرس و جو

در روش عبارت‌های پرس و جو یک عبارت (clause) اضافی from برای تولید یک توالی فرزند به کار برده می‌شود. خروجی کد زیر مشابه کد قبلی است:
 string[] ingredients = {"Sugar","Egg","Milk","Flour","Butter"};
IEnumerable<char> query2 = from i in ingredients
from c in i.ToCharArray()
select c;

foreach (var item in query2)
{
   Console.WriteLine(item);
}

عملگرهای جداکننده (Partitioning Operators)
عملگر‌های جداکننده، یک توالی ورودی را دریافت و آنها را از هم جدا می‌کنند.

Take
عملگر Takeیک توالی ورودی را دریافت کرده و تعداد مشخصی از توالی را باز می‌گرداند.
مثال: عملگر Take، سه عضو اول توالی Ingredient را باز می‌گرداند:
 Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Take(3);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Sugar
Egg
Milk
همچون سایر عملگر‌های پرس و جو، عملگر Take هم می‌تواند بصورت زنجیروار استفاده شود. در مثال زیر ابتدا عملگر Where برای محدود کردن عناصر با شرطی خاص و سپس عملگر Take برای جدا کردن عناصر حاصل از نتیجه‌ی مرحله قبل مورد استفاده قرار گرفته است:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Where(x=>x.Calories>100).Take(2);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
Sugar
Milk

پیاده سازی توسط عبارت‌های پرس و جو

کلمه‌ی کلیدی (Key word) جایگزینی برای عملگر Take وجود ندارد، ولی می‌توان با ترکیب دو روش نوشتن پرس و جو، خروجی مورد نظر را تولید کرد:
 IEnumerable<Ingredient> query =
(from i in ingredients
  where i.Calories > 100
  select i).Take(2);
TakeWhile
عملگر TakeWhile بر عکس عملگر Take تعداد مشخصی را باز می‌گرداند . این عملگر تا زمانی که گزاره با عناصر مطابقت داشته باشد، اجرا می‌شود و در غیر اینصورت خاتمه پیدا می‌کند.
کد زیر تا زمانی که خصوصیت Calorie توالی ورودی بزرگتر و مساوی 100 باشد، عناصر را جدا می‌کند.
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.TakeWhile(x => x.Calories >= 100);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Sugar
Egg
Milk
همانطور که مشاهده می‌کنید، وقتی عملگر TakeWhile به عنصری می‌رسد که گزاره‌ی آن را نقض می‌کند، متوقف می‌شود (در اینجا Flour). در حالی که ممکن است عناصری بعد از Flour وجود داشته باشند که با گزاره‌ی TakeWhile تطابق داشته باشند.

پیاده سازی توسط عبارت‌های پرس و جو
برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو نتیجه‌ی دلخواه حاصل می‌شود.
 
Skip
این عملگر تعداد مشخصی از عناصر را از ابتدای توالی نادیده گرفته و باقی عناصر را باز می‌گرداند.
کد زیر سه عضو اول توالی را نادیده گرفته و مابقی را باز می‌گرداند:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.Skip(3);
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا :
 Flour
Butter

پیاده سازی توسط عبارت‌های پرس و جو

برای این عملگر هم کلمه‌ی کلیدی (Key word) جایگزینی وجود ندارد و با ترکیب دو روش نوشتن پرس و جو، نتیجه‌ی دلخواه حاصل می‌شود.
با ترکیب عملگر Take و Skip می‌توان اطلاعات را به‌صورت صفحه بندی به کاربر ارائه کرد. مثال زیر این حالت را نشان می‌دهد.
IEnumerable<Ingredient> firstPage = ingredients.Take(2);
IEnumerable<Ingredient> secondPage = ingredients.Skip(2).Take(2);
IEnumerable<Ingredient> thirdPage = ingredients.Skip(4).Take(2);

Console.WriteLine("First Page : ");
foreach (var ingredient in firstPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}

Console.WriteLine("Second Page : ");
foreach (var ingredient in secondPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}

Console.WriteLine("Third Page : ");
foreach (var ingredient in thirdPage)
{
   Console.WriteLine(" - " + ingredient.Name);
}
خروجی کد بالا :
 First Page :
 - Sugar
 - Egg
Second Page :
 - Milk
 - Flour
Third Page :
 - Butter
SkipWhile
عملگر SkipWhile، مثل عملگر TakeWhile، از یک گزاره برای ارزیابی عناصر توالی استفاده می‌کند. این عملگر تا زمانیکه عناصر توالی، گزاره را نقض نکنند، عناصر را نادیده می‌گیرد.

مثال:
Ingredient[] ingredients =
{
   new Ingredient{Name = "Sugar", Calories=500},
   new Ingredient{Name = "Egg", Calories=100},
   new Ingredient{Name = "Milk", Calories=150},
   new Ingredient{Name = "Flour", Calories=50},
   new Ingredient{Name = "Butter", Calories=200},
};

IEnumerable<Ingredient> query = ingredients.SkipWhile(x => x.Name != "Milk");
foreach (var ingredient in query)
{
   Console.WriteLine(ingredient.Name);
}
خروجی کد بالا:
 Milk
Flour
Butter
مطالب
نمایش فرم‌های مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
اصول نمایش اطلاعات مودال به کمک bootstrap در مطلب «استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر» بررسی شدند.
در این قسمت قصد داریم یک فرم Ajaxایی را در ASP.NET MVC به همراه تمام مسایل اعتبارسنجی، پردازش اطلاعات و غیره را به کمک Twitter Bootstrap و jQuery Ajax پیاده سازی کنیم.


تهیه افزونه jquery.bootstrap-modal-ajax-form.js

از این جهت که مباحث مرتبط با نمایش و پردازش فرم‌های مودال Ajaxایی به کمک Twitter Bootstrap اندکی نکته دار و طولانی هستند، بهتر است این موارد را به شکل یک افزونه، کپسوله کنیم. کدهای کامل افزونه jquery.bootstrap-modal-ajax-form.js را در ادامه ملاحظه می‌کنید:
// <![CDATA[
(function ($) {
    $.bootstrapModalAjaxForm = function (options) {
        var defaults = {
            renderModalPartialViewUrl: null,
            renderModalPartialViewData: null,
            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();
        };

        var enableBootstrapStyleValidation = function () {
            $.validator.setDefaults({
                highlight: function (element, errorClass, validClass) {
                    if (element.type === 'radio') {
                        this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                    } else {
                        $(element).addClass(errorClass).removeClass(validClass);
                        $(element).closest('.control-group').removeClass('success').addClass('error');
                    }
                    $(element).trigger('highlited');
                },
                unhighlight: function (element, errorClass, validClass) {
                    if (element.type === 'radio') {
                        this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                    } else {
                        $(element).removeClass(errorClass).addClass(validClass);
                        $(element).closest('.control-group').removeClass('error').addClass('success');
                    }
                    $(element).trigger('unhighlited');
                }
            });
        }

        var enablePostbackValidation = function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        }

        var processAjaxForm = function (dialog) {
            $('form', dialog).submit(function (e) {
                e.preventDefault();

                if (!validateForm($(this))) {
                    //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود
                    return false;
                }

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

                //اطلاعات نباید کش شوند
                $.ajaxSetup({ cache: false });
                $.ajax({
                    url: options.postUrl,
                    type: "POST",
                    data: $(this).serialize(),
                    success: function (result) {
                        if (result.success) {
                            $('#dialogDiv').modal('hide');
                            if (options.completeHandler)
                                options.completeHandler();
                        } else {
                            $('#dialogContent').html(result);
                            if (options.errorHandler)
                                options.errorHandler();
                        }
                    }
                });
                return false;
            });
        };

        var mainContainer = "<div id='dialogDiv' class='modal hide fade in'><div id='dialogContent'></div></div>";
        enableBootstrapStyleValidation(); //اعمال نکات خاص بوت استرپ جهت اعتبارسنجی یکپارچه با آن
        $.ajaxSetup({ cache: false });
        $.ajax({
            type: "POST",
            url: options.renderModalPartialViewUrl,
            data: options.renderModalPartialViewData,
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            complete: function (xhr, status) {
                var data = xhr.responseText;
                var data = xhr.responseText;
                if (xhr.status == 403) {
                    window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا می‌شود
                }
                else if (status === 'error' || !data) {
                    if (options.errorHandler)
                        options.errorHandler();
                }
                else {
                    var dialogContainer = "#dialogDiv";
                    $(dialogContainer).remove();
                    $(mainContainer).appendTo('body');

                    $('#dialogContent').html(data); // دریافت پویای اطلاعات مودال دیالوگ
                    $.validator.unobtrusive.parse("#dialogContent"); // فعال سازی اعتبارسنجی فرمی که با ایجکس بارگذاری شده                            
                    enablePostbackValidation();
                    // و سپس نمایش آن به صورت مودال
                    $('#dialogDiv').modal({
                        backdrop: 'static', //با کلیک کاربر روی صفحه، صفحه مودال بسته نمی‌شود
                        keyboard: true
                    }, 'show');
                    // تحت نظر قرار دادن این فرم اضافه شده
                    processAjaxForm('#dialogContent');
                }
            }
        });
    };
})(jQuery);
// ]]>
توضیحات:
- توابع enableBootstrapStyleValidation و enablePostbackValidation در مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» بررسی شدند.
- این افزونه با توجه به مقدار renderModalPartialViewUrl، یک partial view را از برنامه ASP.NET MVC درخواست می‌کند.
- سپس این partial view را به صورت خودکار به صفحه اضافه کرده و آن‌را به صورت modal نمایش می‌دهد.
- پس از افزودن فرم Ajaxایی دریافتی، مسایل اعتبارسنجی را به آن اعمال کرده و سپس دکمه submit آن‌را تحت کنترل قرار می‌دهد.
- در زمان submit، ابتدا بررسی می‌کند که آیا فرم معتبر است و اعتبارسنجی آن بدون مشکل است؟ اگر اینچنین است، اطلاعات فرم را به آدرس postUrl به صورت Ajaxایی ارسال می‌کند.


کدهای مدل برنامه
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Mvc4TwitterBootStrapTest.Models
{
    public class User
    {
        public int Id { set; get; }

        [DisplayName("نام")]
        [Required(ErrorMessage="لطفا نام را تکمیل کنید")]
        public string Name { set; get; }

        [DisplayName("نام خانوادگی")]
        [Required(ErrorMessage = "لطفا نام خانوادگی را تکمیل کنید")]
        public string LastName { set; get; }
    }
}
در اینجا یک مدل ساده‌را به همراه ویژگی‌های اعتبارسنجی و نام‌های نمایشی خواص ملاحظه می‌کنید.


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

using System.Web.Mvc;
using Mvc4TwitterBootStrapTest.Models;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class ModalFormAjaxController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش صفحه اولیه
        }

        [HttpPost] //برای این حالت امن‌تر است
        //[AjaxOnly]
        public ActionResult RenderModalPartialView()
        {
            //رندر پارشال ویوو صفحه مودال به همراه اطلاعات مورد نیاز آن
            return PartialView(viewName: "_ModalPartialView", model: new User { Name = "", LastName = "" });
        }

        [HttpPost]
        //[AjaxOnly]
        public ActionResult Index(User user) //ذخیره سازی اطلاعات
        {
            if (this.ModelState.IsValid)
            {
                //todo: SaveChanges;
                return Json(new { success = true });
            }

            this.ModelState.AddModelError("", "خطایی رخ داده است");
            return PartialView("_ModalPartialView", user);
        }
    }
}
کدهای کنترلر برنامه در این حالت از سه قسمت تشکیل می‌شود:
الف) متد Index حالت HttpGet که صفحه ابتدایی را نمایش خواهد داد.
ب) متد RenderModalPartialView یک partial view اضافه شده به برنامه به نام _ModalPartialView.cshtml را بازگشت می‌دهد. این partial view در حقیقت همان فرمی است که قرار است به صورت مودال نمایش داده شود و پردازش آن نیز Ajaxایی باشد.
ج) متد Index حالت HttpPost که نهایتا اطلاعات فرم مودال را دریافت خواهد کرد. اگر پردازش موفقیت آمیز بود، نیاز است همانند کدهای فوق return Json صورت گیرد. در غیراینصورت مجددا همان partial view را بازگشت دهید.


کدهای Index.cshtml

@{
    ViewBag.Title = "Index";
    var renderModalPartialViewUrl = Url.Action("RenderModalPartialView", "ModalFormAjax");
    var postDataUrl = Url.Action("Index", "ModalFormAjax");
}
<h2>
    Index</h2>
<a href="#" class="btn btn-primary" id="btnCreate">ثبت اطلاعات</a>

@section JavaScript
{
    <script type="text/javascript">
        $(function () {           
            $('#btnCreate').click(function (e) {
                e.preventDefault(); //می‌خواهیم لینک به صورت معمول عمل نکند

                $.bootstrapModalAjaxForm({
                    postUrl: '@postDataUrl',
                    renderModalPartialViewUrl: '@renderModalPartialViewUrl',
                    renderModalPartialViewData: {},
                    loginUrl: '/login',
                    beforePostHandler: function () {                       
                    },
                    completeHandler: function () {
                        // Refresh: برای حالتیکه نیاز به به روز رسانی کامل صفحه زیرین باشد
                        // location.reload();
                    },
                    errorHandler: function () {
                    }
                });
            });
        });             
    </script>
}
این کدها متناظر هستند با کدهای view اکشن متد Index در حالت Get.
- در اینجا یک لینک ساده در صفحه قرار گرفته و به کمک کلاس btn مجموعه bootstrap به شکل یک دکمه مزین شده است.
- در ادامه نحوه استفاده از افزونه‌ای را که در ابتدای بحث طراحی کردیم، ملاحظه می‌کنید. کار با آن بسیار ساده است و تنها باید مسیرهای ارسال اطلاعات نهایی به سرور یا postDataUrl و مسیر دریافت partial view رندر شده یا renderModalPartialViewUrl به آن معرفی شود. سایر مسایل آن خودکار است.


کدهای _ModalPartialView.cshtml یا همان فرم مودال برنامه

@model Mvc4TwitterBootStrapTest.Models.User
<div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
        &times;</button>
    <h5>
        افزودن کاربر جدید</h5>
</div>
@using (Html.BeginForm("Index", " ModalFormAjax", FormMethod.Post, new { @class = "modal-form" }))
{
    <div class="modal-body">
        @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })
        <fieldset class="form-horizontal">
            <legend>مشخصات کاربر</legend>
            <div class="control-group">
                @Html.LabelFor(model => model.Name, new { @class = "control-label" })
                <div class="controls">
                    @Html.EditorFor(model => model.Name)
                    @Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })
                </div>
            </div>
            <div class="control-group">
                @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
                <div class="controls">
                    @Html.EditorFor(model => model.LastName)
                    @Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })
                </div>
            </div>
        </fieldset>
    </div>
        
    <div class="modal-footer">
        <button class="btn btn-primary" type="submit">
            ارسال</button>
        <button class="btn" data-dismiss="modal" aria-hidden="true">
            انصراف</button>
    </div>
}
در اینجا اطلاعات فرمی را ملاحظه می‌کنید که قرار است به صورت مودال نمایش داده شود. نحوه طراحی آن بر اساس نکات form-horizontal است. همچنین divهای modal-header، modal-body و modal-footer نیز به این فرم ویژه اضافه شده‌اند تا به خوبی توسط bootstrap پردازش گردد.
حاصل نهایی این مبحث را در دو شکل ذیل ملاحظه می‌کنید. صفحه index نمایش دهنده یک دکمه و در ادامه باز شدن یک فرم مودال، پس از کلیک بر روی دکمه ثبت اطلاعات.