مطالب
استفاده از date picker شمسی جاوا اسکریپتی در Blazor با قابلیت ورود تاریخ به صورت دستی
دیت پیکرهای گوناگونی توسط افراد مختلف نوشته شده‌اند که هر یک مشکلات خاص خود را دارند. در این مطلب به چگونگی استفاده از یکی از سازگارترین  دیت پیکرهای جاوا اسکریپتی که توسط آقای امیرمسعود ایرانی نوشته شده است در Blazor خواهیم پرداخت.
مهم‌ترین ویژگی این دیت پیکر امکان ورود تاریخ به صورت دستی توسط کاربر است.

فرمت‌های قابل قبول برای ورود تاریخ عبارتند از:
۹۰۰۸۱۴ ۱۴۰۸۹۰ ۱۳۹۰۰۸۱۴ ۱۴/۸/۹۰ ۹۰/۸/۱۴ ۱۴/۸/۱۳۹۰ ۱۳۹۰/۸/۱۴ ۱۴-۸-۹۰ ۹۰-۸-۱۴ ۱۴-۸-۱۳۹۰ ۱۳۹۰-۸-۱۴ 
و فرمت‌های ویژه:
۰۸۱۴ ۱۴۰۸ ۱۴-۸ ۸-۱۴ ۱۴/۸ ۸/۱۴ ۱۴
در فرمت‌های ویژه که سال و ماه وارد نشده‌اند، سال و ماه فعلی به حساب خواهد آمد.
در فرمت‌هایی که سال مشخص نشده باشد، دو رقم ابتدایی در صورت امکان روز محاسبه خواهند شد.
بنابراین قادر خواهیم بود که در خروجی یک فرمت استاندارد داشته باشیم حتی با فرمت‌های مختلفی که کاربر وارد خواهد کرد.

روش به کارگیری تقویم در Blazor

در ابتدا فایل‌های مورد نیاز را دانلود کرده (AMIB_jsPersianCal_0.2.1.rar) و به پروژه اضافه می‌کنیم.
سپس به _layout رفته و ارجاعات زیر را برای افزودن فایل‌های css و js به پروژه اضافه می‌کنیم:
<link href="css/js-persian-cal.css" rel="stylesheet"/>
<script src="js/js-persian-cal.min.js"></script>
حال برای استفاده از دیت پیکر در کامپوننت‌ها از تگ input به شکل زیر استفاده می‌کنیم:
<input type="text" id="pcal1" />
Id آن مهم است زیرا توسط آن به تابع جاوااسکریپتی معرفی می‌شود. می‌توان هر اسمی را اختیار کرد فقط بهتر است تمامی دیت پیکرهای موجود در صفحه یک اسم داشته باشند اما با ایندکس‌های مختلف مانند pcal1، pcal2 و ... . دلیل آن این است که می‌توان تمامی دیت پیکرهای را توسط یک حلقه به تابع مربوطه معرفی کرد.
همانطور که می‌دانید برای استفاده از توابع جاوا اسکریپتی در Blazor از JSRuntime استفاده می‌شود. بنابراین به شکل زیر عمل خواهیم کرد.
protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        int dateFieldCount = 1;
        if (firstRender)
        {
            for (int i = 1; i <= dateFieldCount; i++)
            {
                await JsRuntime.InvokeVoidAsync("CallAmib", "pcal" + i.ToString());
            }
        }
    }
توسط حلقه for تمامی تگ‌های input موجود در کامپوننت را که Id آنها با pcal شروع می‌شود به دیت پیکر تبدیل خواهیم نمود. فقط مقدار متغیر dateFieldCount را باید به تعداد تگ‌های دیت پیکر موجود در کامپوننت تنظیم نمود.
لازم به ذکر است که باید در ابتدای کامپوننت، JSRuntime را به شکل زیر تزریق نمود.
@inject IJSRuntime  JsRuntime
حال فقط کافیست اسکریپت CallAmib را ایجاد کرده و به _layout اضافه نمود.
window.CallAmib = (objCal1) => {
    new AMIB.persianCalendar(objCal1);
}
  بنابراین فایل _layout برنامه الان چیزی شبیه به زیر خواهد بود:
@using Microsoft.AspNetCore.Components.Web
@namespace ShamsiDatePickerBlazor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="css/js-persian-cal.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    @RenderBody()

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="">Reload</a>
        <a>🗙</a>
    </div>
    <script src="js/js-persian-cal.min.js"></script>
    <script src="js/CallAmib.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
تا اینجای کار اگر پروژه را اجرا کنیم، دیت پیکری مانند زیر را خواهیم داشت:

مشکل!!

برای بایند کردن مقدار تاریخ انتخاب شده نمی‌توان از bind-value به طور معمول استفاده کرد؛ زیرا در حقیقت تغییرات input با جاوا اسکریپت انجام می‌گیرد و حالت صفحه تغییری نمی‌کند. برای مرتفع کردن این مشکل نیاز است که در اسکریپت CallAmib متد onchange به شکل زیر صدا زده شده و مقدار تاریخ انتخابی به یک متد داخل کامپوننت ارسال گردیده و در آنجا به یک فیلد منتسب شود.
window.CallCall = (objCal1) => {
    new AMIB.persianCalendar(objCal1,{
        onchange: function(pdate) {
            DotNet.invokeMethodAsync('ShamsiDatePickerBlazor', 'DateChanged', pdate.toString()).then(
                (date) => {
                    console.log(data);
                }
            );
        }
    });
}
توضیحات اسکریپت بالا:
متغیر pdate به صورت توکار مربوط به AMIB.persianCalendar می باشد و مقدار تاریخ انتخابی را در بر دارد.
متد DotNet.invokeMethodAsync یک متد توکار دات نت می‌باشد و برای فراخوانی متدهای سی شارپی از داخل توابع جاوا اسکریپتی به کار می‌رود. آرگومان اول آن در حقیقت نام اسمبلی پروژه می‌باشد. آرگومان دوم آن نام تابع سی شارپی‌است که باید فراخوانی شود و در نهایت آرگومان سوم آن تاریخ انتخاب شده می‌باشد.
در پایان باید متد DateChanged،  به شکل زیر در کامپوننت index نوشته شود:
static string selectedDate;
[JSInvokable]
public static void DateChanged(string pdate)
{
    selectedDate = pdate;
}
این تابع بایستی با صفت [JSInvokable] مزین شود و حتما هم استاتیک باشد.
برای دیدن مقدار جدید selectedDate کافی است روی دکمه ShowNewValue یکبار کلیک نمایید.
نکته: می‌توان به جای input، از InputText مربوط به EditForm هم استفاده نمود. فقط باید یک Id هم به آن انتساب داد. همچنین برای انتساب مقدار دیت پیکر به مدل، باید در متد DateChanged، فیلد مورد نظر از مدل را بجای متغیر selectedDate گذاشت.
شما می‌توانید در اینجا کدهای کامل این مطلب را ملاحظه نمایید.
مطالب دوره‌ها
تولید پویای کد در زمان اجرا توسط Reflection.Emit
در ادامه قصد داریم توسط امکانات Reflection به همراه کدهای IL، اشیایی را در زمان اجرا ایجاد کنیم.


Reflection چیست؟

Reflection چیزهایی هستند که با نگاه در یک آینه قابل مشاهده‌اند. در این حالت شخص می‌تواند قسمت‌های مختلف ظاهر خود را برانداز کرده یا قسمتی را تغییر دهید. اما این مساله چه ربطی به دنیای دات نت دارد؟ در دات نت با استفاده از Reflection می‌توان به اطلاعات اشیاء یک برنامه‌ی در حال اجرا دسترسی یافت. برای مثال نام کلاس‌های مختلف آن چیست یا درون کلاسی خاص، چه متدهایی قرار دارند. همچنین با استفاده از Reflection می‌توان رفتارهای جدیدی را نیز به کلاس‌ها و اشیاء افزود یا آن‌ها را تغییر داد.
همواره عنوان می‌شود که از Reflection به دلیل سربار بالای آن پرهیز کنید و تنها از آن به عنوان آخرین راه حل موجود استفاده نمائید و این دقیقا موردی است که در مباحث جاری بیشتر از آن استفاده خواهد شد: ساخت اشیاء جدید در زمان اجرا به کمک کدهای IL و امکانات Reflection


نگاهی به امکانات متداول Reflection

در مثال بعد، نگاهی خواهیم داشت به امکانات متداول Reflection، مانند دسترسی به متدها و خواص یک کلاس و تعویض مقدار یا فراخوانی آن‌ها:
using System;

namespace FastReflectionTests
{
    class Person
    {
        public string Name { set; get; }

        public string Speak()
        {
            return string.Format("Hello, my name is {0}.", this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //روش متداول
            var vahid = new Person { Name = "Vahid" };
            Console.WriteLine(vahid.Speak());

            var type = vahid.GetType();

            //نمایش متدهای یک کلاس
            var methods = type.GetMethods();
            foreach (var method in methods)
            {
                Console.WriteLine(method.Name);
            }

            //تغییر مقدار یک خاصیت
            var setNameMethod = type.GetMethod("set_Name");
            setNameMethod.Invoke(obj: vahid, parameters: new[] { "Ali" });

            //فراخوانی یک متد
            var speakMethod = type.GetMethod("Speak");
            var result = speakMethod.Invoke(obj: vahid, parameters: null);
            Console.WriteLine(result);
        }
    }
}
با خروجی ذیل
 Hello, my name is Vahid.
set_Name
get_Name
Speak
ToString
Equals
GetHashCode
GetType
Hello, my name is Ali.
توضیحات:
در اینجا یک کلاس شخص با خاصیت نام او تعریف شده است؛ به همراه متدی که رشته‌ای را نمایش خواهد داد.
در متد Main برنامه، ابتدا یک وهله جدید از این شخص ایجاد شده و سپس به روش متداول، متد Speak آن فراخوانی گردیده است. در ادامه کار از امکانات Reflection برای انجام همین امور کمک گرفته شده است.
کار با دریافت نوع یک وهله شروع می‌شود. برای نمونه در اینجا توسط vahid.GetType به نوع وهله ساخته شده دسترسی یافته‌ایم. سپس با داشتن این type، می‌توان به کلیه امکانات Reflection دسترسی یافت. برای مثال توسط GetMethods، لیست کلیه متدهای موجود در کلاس شخص بازگشت داده می‌شود.
اگر به خروجی فوق دقت کنید، پس از سطر اول، 7 سطر بعدی نمایانگر متدهای موجود در کلاس شخص هستند. شاید عنوان کنید که این کلاس به نظر یک متد بیشتر ندارد. اما در دات نت اشیاء از شیء Object مشتق می‌شوند و چهار متد ToString، Equals، GetHashCode و GetType متعلق به آن هستند. همچنین خواص تعریف شده نیز در اصل به دو متد set و get به صورت خودکار در کدهای IL برنامه ترجمه خواهند شد. از همین متد set_Name در ادامه برای مقدار دهی خاصیت نام وهله ایجاد شده استفاده شده است.
همانطور که ملاحظه می‌کنید برای فراخوانی یک وهله از طریق Reflection، ابتدا توسط متد type.GetMethod می‌توان به آن دسترسی یافت و سپس با فراخوانی متد Invoke، می‌توان متد مدنظر را بر روی یک شیء مهیا با پارامترهایی که ذکر می‌کنیم، فراخوانی کرد. اگر این متد پارامتری ندارد، آن‌را نال قرار خواهیم داد.

تا اینجا مقدمه‌ای را ملاحظه نمودید که بیشتر جهت تکمیل بحث، حفظ روابط منطقی قسمت‌های مختلف آن و یادآوری مباحث مرتبط با Reflection ذکر شدند.


ایجاد اشیاء در زمان اجرای برنامه

یکی از کلاس‌های مهم Reflection که در منابع مختلف کمتر به آن پرداخته شده است، کلاس DynamicMethod آن است که از آن می‌توان برای ایجاد اشیاء و یا متدهایی پویا در زمان اجرا استفاده کرد. این کلاس قرار گرفته در فضای نام System.Reflection.Emit، دارای یک ILGenerator است که می‌توان به آن OpCodeهایی را اضافه کرد. زمانیکه کار ایجاد این متدپویا به پایان رسید، با استفاده از Delegates امکان دسترسی و اجرای این متد پویا وجود خواهد داشت.
یک مثال کامل را در این زمینه در ادامه ملاحظه می‌نمائید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static double Divider(int a, int b)
        {
            return a / b;
        }

        delegate double DividerDelegate(int a, int b);
        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Divider(10, 2));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "DividerMethod",
                                        returnType: typeof(double),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(opcode: OpCodes.Ldarg_0); //بارگذاری پارامتر اول بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Ldarg_1); //بارگذاری پارامتر دوم بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Div); // دو پارامتر از پشته ارزیابی دریافت و تقسیم خواهند شد
            il.Emit(opcode: OpCodes.Ret); // دریافت نتیجه نهایی از پشته ارزیابی و بازگشت آن

            //فراخوانی متد پویا
            //روش اول
            var result = myMethod.Invoke(obj: null, parameters: new object[] { 10, 2 });
            Console.WriteLine(result);

            //روش دوم
            var method = (DividerDelegate)myMethod.CreateDelegate(delegateType: typeof(DividerDelegate));
            Console.WriteLine(method(10, 2));
        }
    }
}
توضیحات
در ابتدای این مثال جدید یک متد متداول تقسیم کننده دو عدد را ملاحظه می‌کنید. در ادامه قصد داریم overload دیگری از این متد را توسط کدهای MSIL در زمان اجرا ایجاد کنیم که دو پارامتر int را قبول می‌کند.
کار با وهله سازی کلاس DynamicMethod موجود در فضای نام System.Reflection.Emit شروع می‌شود. در اینجا کار تعریف امضای متد جدید باید صورت گیرد. برای مثال نام آن چیست، نوع خروجی آن کدام است. نوع پارامترهای آن چیست و نهایتا این متدی که قرار است به صورت پویا به برنامه اضافه شود، باید در کجا قرار گیرد. برای اینکار از Module خود کلاس Program برنامه استفاده شده است.
پس از تعریف امضای متد پویا، نوبت به تعریف بدنه‌ی آن می‌رسد. کار با دریافت یک ILGenerator که می‌توان در آن کدهای IL را وارد کرد شروع می‌شود. مابقی آن تعریف کدهای IL توسط متد Emit است و پیشتر با مقدمات اسمبلی دات نت در قسمت‌های قبلی مبحث جاری آشنا شده‌ایم. ابتدا دو Ldarg فراخوانی شده‌اند تا دو پارامتر ورودی متد را دریافت کنند. سپس Div بر روی آن‌ها صورت گرفته و نهایتا نتیجه بازگشت داده شده است.
خوب؛ تا اینجا موفق شدیم اولین متد پویای خود را ایجاد نمائیم. برای اجرا آن حداقل دو روش وجود دارد:
الف) فراخوانی متد Invoke بر روی آن. با توجه به اینکه قرار نیست این متد بر روی وهله‌ی خاصی اجرا شود، اولین پارامتر آن null وارد شده است و سپس پارامترهای این متد پویا توسط آرگومان دوم متد Invoke وارد شده‌اند.
ب) می‌توان این عملیات را اندکی شکیل‌تر کرد. برای اینکار پیش از متد Main برنامه یک delegate به نام DividerDelegate تعریف شده است. سپس با استفاده از متد CreateDelegate، خروجی این متد پویا را تبدیل به یک delegate کرده‌ایم. اینبار فراخوانی متد پویا بسیار شبیه به متدهای معمولی می‌شود.
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت پنجم - نکات و مباحث تکمیلی
پس از بررسی مباحث و نکات پایه‌ای کار با کتابخانه‌ی Moq، در این قسمت تعدادی از نکات تکمیلی آن‌را بررسی خواهیم کرد.


حالت‌های عملکرد کتابخانه‌ی Moq

کتابخانه‌ی Moq، دو حالت عملکرد را دارد: Strict Mode و Loose mode. زمانیکه یک Mock object را نمونه سازی می‌کنیم، به صورت پیش‌فرض کتابخانه‌ی Moq، یک Loose mock را ایجاد می‌کند. در این حالت این شیء، مقادیر پیش‌فرض خواص و اشیاء را بازگشت می‌دهد و استثنائی را صادر نمی‌کند. اگر این موارد مدنظر نیستند، می‌توان به حالت Strict آن رجوع کرد که روش تنظیم آن به صورت زیر است:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);
در این حالت اگر متد آزمون واحد را اجرا کنیم، با پیام زیر، با شکست مواجه خواهد شد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
Moq.MockException: IIdentityVerifier.Initialize() invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
در حالت Strict، تمام فراخوانی‌های شیء Mock شده باید دارای Setup باشند (نیازی به Setup تمام موارد نیست؛ فقط مواردی که در فراخوانی‌های آزمون واحد، مورد استفاده قرار می‌گیرند، حتما باید تنظیم شوند). برای نمونه در اینجا عنوان کرده‌است که در این آزمایش، تنظیمات متد Initialize انجام نشده‌است که با تعریف سطر زیر، این مشکل برطرف می‌شود:
mockIdentityVerifier.Setup(x => x.Initialize());

بنابراین هرچند کارکردن با حالت پیش‌فرض کتابخانه‌ی Moq ساده‌است، اما تنظیم حالت Strict سبب می‌شود تا تنظیمی را فراموش نکنیم و در نتیجه کیفیت آزمون واحد تهیه شده افزایش می‌یابد.


صدور استثناءها از طریق Mock objects

اگر در سیستم در حال آزمایش، قسمتی به بررسی خطاها اختصاص دارد، می‌توان توسط Mock objects استثناءهایی را تولید و به این ترتیب منطق بررسی خطاها را آزمایش کرد.
برای نمونه در متد Process کلاس LoanApplicationProcessor، یک try/catch را به قسمت CalculateScore اضافه می‌کنیم:
try
{
    _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
}
catch
{
    return application.IsAccepted;
}
زمانیکه کار فراخوانی متد CalculateScore صورت می‌گیرد، برای تنظیم آزمون واحد آن می‌توان از متد Throws، برای صدور یک استثناء استفاده کرد:
mockCreditScorer.Setup(x =>
                    x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Throws(new InvalidOperationException("Test Exception"));
صدور این استثناء سبب خواهد شد تا درخواست شخص، رد شود. بنابراین در آزمایش آن می‌توان این مساله را بررسی کرد و از رسیدن به این قسمت (رد شدن درخواست) اطمینان حاصل نمود:
Assert.IsFalse(application.IsAccepted);


صدور رخدادها از طریق Mock objects

فرض کنید یک EventArgs سفارشی را به صورت زیر تعریف:
using System;

namespace Loans.Models
{
    public class CreditScoreResultArgs : EventArgs
    {
        public int Score { get; set; }
    }
}
و سپس رخدادی را به نحو زیر به ICreditScorer اضافه کرده‌ایم:
public interface ICreditScorer
{
   event EventHandler<CreditScoreResultArgs> ResultAvailable;
برای اینکه یک Mock object سبب بروز رخداد ResultAvailable شود (به صورت دستی و دقیقا در سطری که مشخص می‌کنیم)، می‌توان به صورت زیر عمل کرد:
mockCreditScorer.Raise(x => x.ResultAvailable += null, new CreditScoreResultArgs());
ابتدا توسط متد Raise، رخ‌داد مدنظر را ذکر می‌کنیم و سپس یک نمونه‌ی EventArgs را به آن ارسال خواهیم کرد.
روش دیگر انجام اینکار به صورت زیر است:
mockCreditScorer.Setup(x =>
                x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Raises(x => x.ResultAvailable += null, new CreditScoreResultArgs());
در این حالت با فراخوانی متد CalculateScore، رخداد ResultAvailable به صورت خودکار صادر می‌شود.


معرفی Partial Mocks

در اغلب آزمون‌های واحدی که تا اینجا بررسی شدند، ابتدا یک Mock object را ایجاد و سپس وهله‌ای از سرویس مدنظر را توسط آن تهیه می‌کنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی می‌کنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت می‌گیرد. در نهایت حالت و یا نتیجه‌ای را دریافت می‌کنیم و آن‌را با حالت یا نتیجه‌ای که انتظار داریم، مقایسه خواهیم کرد. در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجه‌ی نهایی حاصل از عملکرد آن، مورد بررسی قرار می‌گیرد. این بررسی‌ها را نیز بر روی اینترفیس‌ها انجام دادیم. اگر بجای اینترفیس‌ها از یک class استفاده شود، به آن partial mock گفته می‌شود. عموما مواردی را که آزمایش آن‌ها سخت است، با Partial mocks پیاده سازی می‌کنند؛ مانند کار با فایل سیستم، کار با قطعه کدهای نامعین مانند DateTime.Now، اعداد اتفاقی و یا Guidها.

در مثال زیر، شبیه به متد آزمون واحد Accept که تاکنون آن‌را بررسی کردیم، از اشیاء Mock شده استفاده شده‌است؛ با یک تفاوت: بجای اینترفیس IIdentityVerifier، از کلاس پیاده سازی کننده‌ی آن که در اینجا IdentityVerifierServiceGateway است، استفاده شده:
namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void AcceptUsingPartialMock()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>();

            mockIdentityVerifier.Setup(x => x.CallService(applicant.Name, applicant.Age, applicant.Address))
                .Returns(true);

            var mockCreditScorer = new Mock<ICreditScorer>();
            mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);

            var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            sut.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
در اینجا برای اینکه بتوانیم متد CallService را که private بوده، بررسی و تنظیم کنیم، آن‌را به public virtual تبدیل کرده‌ایم تا توسط Moq قابل دسترسی و همچنین قابل بازنویسی شود:
public virtual bool CallService(string applicantName, int applicantAge, string applicantAddress)


تبدیل DateTime.Now به یک مقدار ثابت قابل آزمایش توسط Partial Mocks

در کلاس IdentityVerifierServiceGateway، یک چنین کدی را داریم که از DateTime.Now نامشخص استفاده می‌کند و آزمون واحد نوشتن برای آن مشکل است؛ چون DateTime.Now در هربار که آزمایش اجرا می‌شود، تغییر می‌کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = DateTime.Now;
    Disconnect();

    return isValidIdentity;
}
برای بالابردن قابلیت آزمون نویسی این کلاس، آن‌را به صورت زیر Refactor می‌کنیم تا DateTime.Now را به صورت یک متد public virtual دریافت کند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress)
{
    Connect();
    var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
    LastCheckTime = GetCurrentTime();
    Disconnect();

    return isValidIdentity;
}

public virtual DateTime GetCurrentTime()
{
    return DateTime.Now;
}
اکنون آزمون واحد نویسی برای این کلاس توسط Mock objects بسیار ساده‌است:
var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);
// ...
Assert.AreEqual(expectedTime, mockIdentityVerifier.Object.LastCheckTime);
در اینجا خروجی متد GetCurrentTime بر روی Mock object تهیه شده، به یک مقدار ثابت تنظیم شده‌است که با هر بار اجرای آزمایش در زمان‌های مختلف، تغییری نمی‌کند و وابسته‌ی به DateTime.Now نامشخص، نیست.


استفاده از متدهای protected بجای استفاده از متدهای public virtual در Partial Mocks

همانطور که مشاهده کردید، برای کار با Partial Mocks نیاز است متدهای معرفی شده، از نوع public virtual باشند. برای نمونه حتی مجبور شدیم یک متد private را نیز public کنیم. اگر علاقمند به این نوع تغییرات نیستید، می‌توان بجای public کردن متدهای private، آن‌ها را protected تعریف کرد. به همین جهت دو متدی را که تاکنون public virtual تعریف کردیم، تبدیل به protected virtual می‌کنیم.
پس از آن در کلاسی که آزمون‌های واحد را تهیه کردیم، ابتدا using Moq.Protected را ذکر می‌کنیم تا بتوانیم به قابلیت‌های ویژه‌ی کار با متدهای Protected دسترسی پیدا کنیم.
سپس روش تنظیم این نوع متدهای protected، چون دسترسی مستقیمی به آن‌ها وجود ندارد، به صورت زیر، با ذکر نام رشته‌ای آن‌ها تغییر می‌کند:
mockIdentityVerifier.Protected().Setup<bool>(
        "CallService",applicant.Name, applicant.Age, applicant.Address)
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected().Setup<DateTime>("GetCurrentTime")
    .Returns(expectedTime);
ابتدا متد Protected شیء Mock شده ذکر می‌شود و پس از آن متد Setup باید دقیقا نوع بازگشتی متد در حال تنظیم را ذکر کند؛ چون دیگر دسترسی strongly typed ای به آن نداریم. پس ا‌ز آن، لیست پارامترهای متد، ذکر می‌شوند.

روش دیگری نیز برای تعریف متدهای protected وجود دارد که اینبار strongly typed است. بالای متد آزمون واحد، اینترفیس private زیر را تعریف می‌کنیم:
interface IIdentityVerifierServiceGatewayProtectedMembers
{
   DateTime GetCurrentTime();
   bool CallService(string applicantName, int applicantAge, string applicantAddress);
}
که در آن متدهای تعریف شده، با متدهای protected در حال بررسی، امضای یکسانی دارند (و همواره با هر تغییری در برنامه نیز باید این وضعیت حفظ شود). در ادامه تعاریف تنظیمات این متدها به صورت strongly typed زیر قابل انجام است:
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.CallService(It.IsAny<string>(),
        It.IsAny<int>(),
        It.IsAny<string>()))
    .Returns(true);

var expectedTime = new DateTime(2000, 1, 1);
mockIdentityVerifier.Protected()
    .As<IIdentityVerifierServiceGatewayProtectedMembers>()
    .Setup(x => x.GetCurrentTime())
    .Returns(expectedTime);


معرفی روش دیگری بجای استفاده از متدهای protected

اگر در کدهای خود نیاز به استفاده‌ی بیش از حد از متدهای protected را مشاهده کردید، این مورد می‌توان نشانه‌ی امکان Refactoring این قسمت از کدها به سرویس‌هایی مجزا باشند. برای مثال می‌توان یک اینترفیس INowProvider را به صورت زیر تعریف کرد:
using System;

namespace Loans.Services.Contracts
{
    public interface INowProvider
    {
        DateTime GetNow();
    }
}
و سپس آن‌را به سازنده‌ی کلاس IdentityVerifierServiceGateway تزریق کرد:
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        private readonly INowProvider _nowProvider;
        
        public DateTime LastCheckTime { get; private set; }

        public IdentityVerifierServiceGateway(INowProvider nowProvider)
        {
            _nowProvider = nowProvider;
        }
 و متد GetCurrentTime را حذف و آن‌را با متد GetNow این سرویس جایگزین نمود:
        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = _nowProvider.GetNow();
            // ...
 به این ترتیب نیاز به تنظیم متد protected بازگشت زمان، حذف شده و می‌توان از این سرویس جدید استفاده کرد:
var mockNowProvider = new Mock<INowProvider>();
mockNowProvider.Setup(x => x.GetNow()).Returns(expectedTime);

var mockIdentityVerifier =  new Mock<IdentityVerifierServiceGateway>(mockNowProvider.Object);


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید: MoqSeries-05.zip
مطالب
ASP.NET MVC #12

تولید خودکار فرم‌های ورود و نمایش اطلاعات در ASP.NET MVC بر اساس اطلاعات مدل‌ها

در الگوی MVC، قسمت M یا مدل آن یک سری ویژگی‌های خاص خودش را دارد:
شما را وادار نمی‌کند که مدل را به نحو خاصی طراحی کنید. شما را مجبور نمی‌کند که کلاس‌های مدل را برای نمونه همانند کلاس‌های کنترلرها، از کلاس خاصی به ارث ببرید. یا حتی در مورد نحوه‌ی دسترسی به داده‌ها نیز، نظری ندارد. به عبارتی برنامه نویس است که می‌تواند بر اساس امکانات مهیای در کل اکوسیستم دات نت، در این مورد آزادانه تصمیم گیری کند.
بر همین اساس ASP.NET MVC یک سری قرارداد را برای سهولت اعتبار سنجی یا تولید بهتر رابط کاربری بر اساس اطلاعات مدل‌ها، فراهم آورده است. این قراردادها هم چیزی نیستند جز یک سری metadata که نحوه‌ی دربرگیری اطلاعات را در مدل‌ها توضیح می‌دهند. برای دسترسی به آن‌ها پروژه جاری باید ارجاعی را به اسمبلی‌های System.ComponentModel.DataAnnotations.dll و System.Web.Mvc.dll داشته باشد (که VS.NET به صورت خودکار در ابتدای ایجاد پروژه اینکار را انجام می‌دهد).

یک مثال کاربردی

یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. در پوشه مدل‌های آن، مدل اولیه‌ای را با محتوای زیر ایجاد نمائید:

using System;

namespace MvcApplication8.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
public decimal Salary { set; get; }
public string Address { set; get; }
public bool IsMale { set; get; }
public DateTime AddDate { set; get; }
}
}

سپس یک کنترلر جدید را هم به نام EmployeeController با محتوای زیر به پروژه اضافه نمائید:

using System;
using System.Web.Mvc;
using MvcApplication8.Models;

namespace MvcApplication8.Controllers
{
public class EmployeeController : Controller
{
public ActionResult Create()
{
var employee = new Employee { AddDate = DateTime.Now };
return View(employee);
}
}
}

بر روی متد Create کلیک راست کرده و یک View ساده را برای آن ایجاد نمائید. سپس محتوای این View را به صورت زیر تغییر دهید:

@model MvcApplication8.Models.Employee
@{
ViewBag.Title = "Create";
}

<h2>Create An Employee</h2>

@using (Html.BeginForm(actionName: "Create", controllerName: "Employee"))
{
@Html.EditorForModel()
<input type="submit" value="Save" />
}

اکنون اگر پروژه را اجرا کرده و مسیر http://localhost/employee/create را وارد نمائید، یک صفحه ورود اطلاعات تولید شده به صورت خودکار را مشاهده خواهید کرد. متد Html.EditorForModel بر اساس اطلاعات خواص عمومی مدل، یک فرم خودکار را تشکیل می‌دهد.
البته فرم تولیدی به این شکل شاید آنچنان مطلوب نباشد، از این جهت که برای مثال Id را هم لحاظ کرده، در صورتیکه قرار است این Id توسط بانک اطلاعاتی انتساب داده شود و نیازی نیست تا کاربر آن‌را وارد نماید. یا مثلا برچسب AddDate نباید به این شکل صرفا بر اساس نام خاصیت متناظر با آن تولید شود و مواردی از این دست. به عبارتی نیاز به سفارشی سازی کار این فرم ساز توکار ASP.NET MVC وجود دارد که ادامه بحث جاری را تشکیل خواهد داد.



سفارشی سازی فرم ساز توکار ASP.NET MVC با کمک Metadata خواص

برای اینکه بتوان نحوه نمایش فرم خودکار تولید شده را سفارشی کرد، می‌توان از یک سری attribute و data annotations توکار دات نت و ASP.NET MVC استفاده کرد و نهایتا این metadata توسط فریم ورک، مورد استفاده قرار خواهند گرفت. برای مثال:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace MvcApplication8.Models
{
public class Employee
{
//[ScaffoldColumn(false)]

[HiddenInput(DisplayValue=false)]
public int Id { set; get; }

public string Name { set; get; }

[DisplayName("Annual Salary ($)")]
public decimal Salary { set; get; }

public string Address { set; get; }

[DisplayName("Is Male?")]
public bool IsMale { set; get; }

[DisplayName("Start Date")]
[DataType(DataType.Date)]
public DateTime AddDate { set; get; }
}
}

در اینجا به کمک ویژگی HiddenInput از نمایش عمومی خاصیت Id جلوگیری خواهیم کرد یا توسط ویژگی DisplayName، برچسب دلخواه خود را به عناصر فرم تشکیل شده، انتساب خواهیم داد. اگر نیاز باشد تا خاصیتی کلا از رابط کاربری حذف شود می‌توان از ویژگی ScaffoldColumn با مقدار false استفاده کرد. یا توسط DataType، مشخص کرده‌ایم که نوع ورودی فقط قرار است Date باشد و نیازی به قسمت Time آن نداریم.
DataType شامل نوع‌های از پیش تعریف شده دیگری نیز هست. برای مثال اگر نیاز به نمایش TextArea بود از مقدار MultilineText، ‌استفاده کنید:

[DataType(DataType.MultilineText)]

یا برای نمایش PasswordBox از مقدار Password می‌توان کمک گرفت. اگر نیاز دارید تا آدرس ایمیلی به شکل یک لینک mailto نمایش داده شود از مقدار EmailAddress استفاده کنید. به کمک مقدار Url، متن خروجی به صورت خودکار تبدیل به یک آدرس قابل کلیک خواهد شد.
اکنون اگر پروژه را مجددا کامپایل کنیم و به آدرس ایجاد یک کارمند جدید مراجعه نمائیم، با رابط کاربری بهتری مواجه خواهیم شد.



سفارشی سازی ظاهر فرم ساز توکار ASP.NET MVC

در ادامه اگر بخواهیم ظاهر این فرم را اندکی سفارشی‌تر کنیم، بهتر است به سورس صفحه تولیدی در مرورگر مراجعه کنیم. در اینجا یک سری عناصر HTML محصور شده با div را خواهیم یافت. هر کدام از این‌ها هم با classهای css خاص خود تعریف شده‌اند. بنابراین اگر علاقمند باشیم که رنگ و قلم و غیره این موارد تغییر دهیم، تنها کافی است فایل css برنامه را ویرایش کنیم و نیازی به دستکاری مستقیم کدهای برنامه نیست.



انتساب قالب‌های سفارشی به خواص یک شیء

تا اینجا در مورد نحوه سفارشی سازی رنگ، قلم، برچسب و نوع داده‌های هر کدام از عناصر نهایی نمایش داده شده، توضیحاتی را ملاحظه نمودید.
در فرم تولیدی نهایی، خاصیت bool تعریف شده به صورت خودکار به یک checkbox تبدیل شده است. چقدر خوب می‌شد اگر امکان تبدیل آن مثلا به RadioButton انتخاب مرد یا زن بودن کارمند ثبت شده در سیستم وجود داشت. برای اصلاح یا تغییر این مورد، باز هم می‌توان از متادیتای خواص، جهت تعریف قالبی خاص برای هر کدام از خواص مدل استفاده کرد.
به پوشه Views/Shared مراجعه کرده و یک پوشه جدید به نام EditorTemplates را ایجاد نمائید. بر روی این پوشه کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه «Create as a partial view» را انتخاب نمائید و نام آن‌را هم مثلا GenderOptions وارد کنید. همچنین گزینه «Create a strongly typed view» را نیز انتخاب کنید. مقدار Model class را مساوی bool وارد نمائید. فعلا یک hello داخل این صفحه جدید وارد کرده و سپس خاصیت IsMale را به نحو زیر تغییر دهید:

[DisplayName("Gender")]
[UIHint("GenderOptions")]
public bool IsMale { set; get; }

توسط ویژگی UIHint، می‌توان یک خاصیت را به یک partial view متصل کرد. در اینجا خاصیت IsMale به partial view ایی به نام GenderOptions متصل شده است. اکنون اگر برنامه را کامپایل و اجرا کرده و آدرس ایجاد یک کارمند جدید را ملاحظه کنید، بجای Checkbox باید یک hello نمایش داده شود.
محتویات این Partial view هم نهایتا به شکل زیر خواهند بود:

@model bool
<p>@Html.RadioButton("", false, !Model) Female</p>
<p>@Html.RadioButton("", true, Model) Male</p>

در اینجا Model که از نوع bool تعریف شده، به خاصیت IsMale اشاره خواهد کرد. دو RadioButton هم برای انتخاب بین حالت زن و مرد تعریف شده‌اند.

یا یک مثال جالب دیگر در این زمینه می‌تواند تبدیل enum به یک Dropdownlist باشد. در این حالت partial view ما شکل زیر را خواهد یافت:

@model Enum
@Html.DropDownListFor(m => m, Enum.GetValues(Model.GetType())
.Cast<Enum>()
.Select(m => {
string enumVal = Enum.GetName(Model.GetType(), m);
return new SelectListItem() {
Selected = (Model.ToString() == enumVal),
Text = enumVal,
Value = enumVal
};
}))

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

[UIHint("Enum")]


سایر متدهای کمکی تولید و نمایش خودکار اطلاعات از روی اطلاعات مدل‌های برنامه

متدهای دیگری نیز در رده‌ی Templated helpers قرار می‌گیرند. اگر از متد Html.EditorFor استفاده کنیم، از تمام این اطلاعات متادیتای تعریف شده نیز استفاده خواهد کرد. همانطور که در قسمت قبل (قسمت 11) نیز توضیح داده شد، صفحه استاندارد Add view در VS.NET به همراه یک سری قالب تولید فرم‌های Create و Edit هم هست که دقیقا کد نهایی تولیدی را بر اساس همین متد تولید می‌کند.
استفاده از Html.EditorFor انعطاف پذیری بیشتری را به همراه دارد. برای مثال اگر یک طراح وب، طرح ویژه‌ای را در مورد ظاهر فرم‌های سایت به شما ارائه دهد، بهتر است از این روش استفاده کنید. اما خروجی نهایی Html.EditorForModel به کمک تعدادی متادیتا و اندکی دستکاری CSS، از دیدگاه یک برنامه نویس بی نقص است!
به علاوه، متد Html.DisplayForModel نیز مهیا است. بجای اینکه کار تولید رابط کاربری اطلاعات نمایش جزئیات یک شیء را انجام دهید، اجازه دهید تا متد Html.DisplayForModel اینکار را انجام دهد. سفارشی سازی آن نیز همانند قبل است و بر اساس متادیتای خواص انجام می‌شود. در این حالت، مسیر پیش فرض جستجوی قالب‌های UIHint آن، Views/Shared/DisplayTemplates می‌باشد. همچنین Html.DisplayFor نیز جهت کار با یک خاصیت مدل تدارک دیده شده است. البته باید درنظر داشت که استفاده از پوشه Views/Shared اجباری نیست. برای مثال اگر از پوشه Views/Home/DisplayTemplates استفاده کنیم، قالب‌های سفارشی تهیه شده تنها جهت Viewهای کنترلر home قابل استفاده خواهند بود.
یکی دیگر از ویژگی‌هایی که جهت سفارشی سازی نحوه نمایش خودکار اطلاعات می‌تواند مورد استفاده قرار گیرد، DisplayFormat است. برای مثال اگر مقدار خاصیت در حال نمایش نال بود، می‌توان مقدار دیگری را نمایش داد:

[DisplayFormat(NullDisplayText = "-")]

یا اگر علاقمند بودیم که فرمت اطلاعات در حال نمایش را تغییر دهیم، به نحو زیر می‌توان عمل کرد:

[DisplayFormat(DataFormatString  = "{0:n}")]

مقدار DataFormatString در پشت صحنه در متد string.Format مورد استفاده قرار می‌گیرد.
و اگر بخواهیم که این ویژگی در حالت تولید فرم ویرایش نیز درنظر گرفته شود، می‌توان خاصیت ApplyFormatInEditMode را نیز مقدار دهی کرد:

[DisplayFormat(DataFormatString  = "{0:n}", ApplyFormatInEditMode = true)]



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

یکی دیگر از قرارداهای بکارگرفته شده در حین استفاده از قالب‌های سفارشی، استفاده از نام اشیاء می‌باشد. مثلا در پوشه Views/Shared/DisplayTemplates، اگر یک Partial view به نام String.cshtml وجود داشته باشد، از این پس نحوه رندر کلیه خواص رشته‌ای تمام مدل‌ها، بر اساس محتوای فایل String.cshtml مشخص می‌شود؛ به همین ترتیب در مورد datetime و سایر انواع مهیا.
برای مثال اگر خواستید تمام تاریخ‌های میلادی دریافتی از بانک اطلاعاتی را شمسی نمایش دهید، فقط کافی است یک فایل datetime.cshtml سفارشی را تولید کنید که Model آن تاریخ میلادی دریافتی است و نهایتا کار این Partial view، رندر تاریخ تبدیل شده به همراه تگ‌های سفارشی مورد نظر می‌باشد. در این حالت نیازی به ذکر ویژگی UIHint نیز نخواهد بود و همه چیز خودکار است.
به همین ترتیب اگر نام مدل ما Employee باشد و فایل Partial view ایی به نام Employee.cshtml در پوشه Views/Shared/DisplayTemplates قرار گیرد، متد Html.DisplayForModel به صورت پیش فرض از محتوای این فایل جهت رندر اطلاعات نمایش جزئیات شیء Employee استفاده خواهد کرد.
داخل Partial viewهای سفارشی تعریف شده به کمک خاصیت ViewData.TemplateInfo.FormattedModelValue مقدار نهایی فرمت شده قابل استفاده را فراهم می‌کند. این مورد هم از این جهت حائز اهمیت است که نیازی نباشد تا ویژگی DisplayFormat را به صورت دستی پردازش کنیم. همچنین اطلاعات ViewData.ModelMetadata نیز دراینجا قابل دسترسی هستند.



سؤال: Partial View چیست؟

همانطور که از نام Partial view بر‌می‌آید، هدف آن رندر کردن قسمتی از صفحه است به همراه استفاده مجدد از کدهای تولید رابط کاربری در چندین و چند View؛ چیزی شبیه به User controls در ASP.NET Web forms البته با این تفاوت که Page life cycle و Code behind و سایر موارد مشابه آن در اینجا حذف شده‌اند. همچنین از Partial viewها برای به روز رسانی قسمتی از صفحه حین فراخوانی‌های Ajaxایی نیز استفاده می‌شود. مهم‌ترین کاربرد Partial views علاوه بر استفاده مجدد از کدها، خلوت کردن Viewهای شلوغ است جهت ساده‌تر سازی نگهداری آن‌ها در طول زمان (یک نوع Refactoring فایل‌های View محسوب می‌شوند).
پسوند این فایل‌ها نیز بسته به موتور View مورد استفاده تعیین می‌شود. برای مثال حین استفاده از Razor، پسوند Partial views همان cshtml یا vbhtml می‌باشد. یا اگر از web forms view engine استفاده شود، پسوند آن‌ها ascx است (همانند User controls در وب فرم‌ها).
البته چون در حالت استفاده از موتور Razor، پسوند View و Partial viewها یکی است، مرسوم شده است که نام Partial viewها را با یک underline شروع کنیم تا بتوان بین این دو تمایز قائل شد.
اگر این فایل‌ها را در پوشه Views/Shared تعریف کنیم، در تمام Viewها قابل استفاده خواهند بود. اما اگر مثلا در پوشه Views/Home آن‌هارا قرار دهیم، تنها در Viewهای متعلق به کنترلر Home، قابل بکارگیری می‌باشند.
Partial views را نیز می‌توان strongly typed تعریف کرد و به این ترتیب با مشخص سازی دقیق نوع model آن، علاوه بر بهره‌مندی از Intellisense خودکار، رندر آن‌را نیز تحت کنترل کامپایلر قرار داد.
مقدار Model در یک View بر اساس اطلاعات مدلی که به آن ارسال شده است تعیین می‌گردد. اما در یک Partial view که جزئی از یک View را نهایتا تشکیل خواهد داد، بر اساس مقدار ارسالی از طریق View معین می‌گردد.

یک مثال
در ادامه قصد داریم کد حلقه نمایش لیستی از عناصر تولید شده توسط VS.NET را به یک Partial view منتقل و Refactor کنیم.
ابتدا یک منبع داده فرضی زیر را در نظر بگیرید:
using System;
using System.Collections.Generic;

namespace MvcApplication8.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Id = 1, AddDate = DateTime.Now.AddYears(-3), Name = "Emp-01", Salary = 3000},
new Employee { Id = 2, AddDate = DateTime.Now.AddYears(-2), Name = "Emp-02", Salary = 2000},
new Employee { Id = 3, AddDate = DateTime.Now.AddYears(-1), Name = "Emp-03", Salary = 1000}
};
}
}
}

سپس از آن در یک کنترلر برای بازگشت لیستی از کارکنان استفاده خواهیم کرد:

public ActionResult EmployeeList()
{
var list = new Employees().CreateEmployees();
return View(list);
}

View متناظر با این متد را هم با کلیک راست بر روی متد، انتخاب گزینه Add view و سپس ایجاد یک strongly typed view از نوع کلاس Employee، ایجاد خواهیم کرد.
در ادامه قصد داریم بدنه حلقه زیر را refactor کنیم و آن‌را به یک Parial view منتقل نمائیم تا View ما اندکی خلوت‌تر و مفهوم‌تر شود:

@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsMale)
</td>
<td>
@Html.DisplayFor(modelItem => item.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}

سپس بر روی پوشه Views/Employee کلیک راست کرده و گزینه Add|View را انتخاب کنید. در اینجا نام _EmployeeItem را وارد کرده و همچنین گزینه Create as a partial view و create a strongly typed view را نیز انتخاب کنید. نوع مدل هم Employee خواهد بود. به این ترتیب فایل زیر تشکیل خواهد شد:
\Views\Employee\_EmployeeItem.cshtml

ابتدای نام فایل‌را با underline شروع کرده‌ایم تا بتوان بین Viewها و Partial views تفاوت قائل شد. همچنین این Partial view چون داخل پوشه Employee تعریف شده، فقط در Viewهای کنترلر Employee در دسترس خواهد بود.
در ادامه کل بدنه حلقه فوق را cut کرده و در این فایل جدید paste نمائید. مرحله اول refactoring یک view به همین نحو آغاز می‌شود. البته در این حالت قادر به استفاده از Partial view نخواهیم بود چون اطلاعاتی که به این فایل ارسال می‌گردد و مدلی که در دسترس آن است از نوع Employee است و نه لیستی از کارمندان. به همین جهت باید item را با Model جایگزین کرد:

@model MvcApplication8.Models.Employee

<tr>
<td>
@Html.DisplayFor(x => x.Name)
</td>
<td>
@Html.DisplayFor(x => x.Salary)
</td>
<td>
@Html.DisplayFor(x => x.Address)
</td>
<td>
@Html.DisplayFor(x => x.IsMale)
</td>
<td>
@Html.DisplayFor(x => x.AddDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
@Html.ActionLink("Details", "Details", new { id = Model.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = Model.Id })
</td>
</tr>


سپس برای استفاده از این Partial view در صفحه نمایش لیست کارمندان خواهیم داشت:

@foreach (var item in Model) {
@Html.Partial("_EmployeeItem", item)
}

متد Html.Partial، اطلاعات یک Partial view را پردازش و تبدیل به یک رشته کرده و در اختیار Razor قرار می‌دهد تا در صفحه نمایش داده شود. پارامتر اول آن نام Partial view مورد نظر است (نیازی به ذکر پسوند فایل نیست) و پارامتر دوم، اطلاعاتی است که به آن ارسال خواهد شد.
متد دیگری هم وجود دارد به نام Html.RenderPartial. کار این متد نوشتن مستقیم در Response است، برخلاف Html.Partial که فقط یک رشته را بر می‌گرداند.



نمایش اطلاعات از کنترلر‌های مختلف در یک صفحه

Html.Partial بر اساس اطلاعات مدل ارسالی از یک کنترلر، کار رندر قسمتی از آن‌را در یک View خاص عهده دار خواهد شد. اما اگر بخواهیم مثلا در یک صفحه یک قسمت را به نمایش آخرین اخبار و یک قسمت را به نمایش آخرین وضعیت آب و هوا اختصاص دهیم، از روش دیگری به نام RenderAction می‌توان کمک گرفت. در اینجا هم دو متد Html.Action و Html.RenderAction وجود دارند. اولی یک رشته را بر می‌گرداند و دومی اطلاعات را مستقیما در Response درج می‌کند.

یک مثال:
کنترلر جدیدی را به نام MenuController به پروژه اضافه کنید:
using System.Web.Mvc;

namespace MvcApplication8.Controllers
{
public class MenuController : Controller
{
[ChildActionOnly]
public ActionResult ShowMenu(string options)
{
return PartialView(viewName: "_ShowMenu", model: options);
}
}
}

سپس بر روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در اینجا قصد داریم یک partial view که نامش با underline شروع می‌شود را اضافه کنیم. مثلا با محتوای زیر ( با توجه به اینکه مدل ارسالی از نوع رشته‌ای است):

@model string

<ul>
<li>
@Model
</li>
</ul>

حین فراخوانی متد Html.Action، یک متد در یک کنترلر فراخوانی خواهد شد (که شامل ارائه درخواست و طی سیکل کامل پردازشی آن کنترلر نیز خواهد بود). سپس آن متد با بازگشت دادن یک PartialView، اطلاعات پردازش شده یک partial view را به فراخوان بازگشت می‌دهد. اگر نامی ذکر نشود، همان نام متد در نظر گرفته خواهد شد. البته از آنجائیکه در این مثال در ابتدای نام Partial view یک underline قرار دادیم، نیاز خواهد بود تا این نام صریحا ذکر گردد (چون دیگر هم نام متد یا ActionName آن نیست). ویژگی ChildActionOnly سبب می‌شود تا این متد ویژه تنها از طریق فراخوانی Html.Action در دسترس باشد.
برای استفاده از آن هم در Viewایی دیگر خواهیم داشت:

@Html.Action(actionName: "ShowMenu", controllerName: "Menu", 
routeValues: new { options = "some data..." })

در اینجا هم پارامتر ارسالی به کمک anonymously typed objects مشخص و مقدار دهی شده است.


سؤال مهم: چه تفاوتی بین RenderPartial و RenderAction وجود دارد؟ به نظر هر دو یک کار را انجام می‌دهند، هر دو مقداری HTML را پس از پرداش به صفحه تزریق می‌کنند.
پاسخ: اگر View والد، دارای کلیه اطلاعات لازم جهت نمایش اطلاعات Partial view است، از RenderPartial استفاده کنید. به این ترتیب برخلاف حالت RenderAction درخواست جدیدی به ASP.NET Pipeline صادر نشده و کارآیی نهایی بهتر خواهد بود. صرفا یک الحاق ساده به صفحه انجام خواهد شد.
اما اگر برای رندر کردن این قسمت از صفحه که قرار است اضافه شود، نیاز به دریافت اطلاعات دیگری خارج از اطلاعات مهیا می‌باشد، از روش RenderAction استفاده کنید. برای مثال اگر در صفحه جاری قرار است لیست پروژه‌ها نمایش داده شود و در کنار صفحه مثلا منوی خاصی باید قرار گیرد، اطلاعات این منو در View جاری فراهم نیست (و همچنین مرتبط به آن هم نیست). بنابراین از روش RenderAction برای حل این مساله می‌توان کمک گرفت.
به صورت خلاصه برای نمایش اطلاعات تکراری در صفحات مختلف سایت در حالتیکه این اطلاعات از قسمت‌های دیگر صفحه ایزوله است (مثلا نمایش چند ویجت مختلف در صفحه)، روش RenderAction ارجحیت دارد.


یک نکته
فراخوانی متدهای RenderAction و RenderPartial در حین کار با Razor باید به شکل فراخوانی یک متد داخل {} باشند:

@{ Html.RenderAction("About"); }
And not @Html.RenderAction("About")

علت این است که @ به تنهایی به معنای نوشتن در Response است. متد RenderAction هم خروجی ندارد و مستقیما در Response اطلاعات خودش را درج می‌کند. بنابراین این دو با هم همخوانی ندارند و باید به شکل یک متد معمولی با آن رفتار کرد.
اگر حجم اطلاعاتی که قرار است در صفحه درج شود بالا است، متدهای RenderAction و RenderPartial نسبت به Html.Action و Html.Partial کارآیی بهتری دارند؛ چون یک مرحله تبدیل کل اطلاعات به رشته و سپس درج نتیجه در Response، در آن‌ها حذف شده است.


مطالب
امکان داشتن خروجی‌های Covariant در C# 9.0
در زبان #C، زمانیکه از کلاسی ارث‌بری می‌شود، امکان بازنویسی متدهای کلاس پایه، در صورت معرفی آن‌ها به صورت virtual و یا abstract، وجود دارد؛ اما در این بازنویسی‌ها، تغییر نوع خروجی این متدها مجاز نیست. این محدودیت در C# 9.0 با معرفی Covariant returns برداشته شده‌است؛ با یک شرط: نوع جدید این خروجی، باید covariant نوع اصلی متدی باشد که از کلاس پایه‌ی آن ارث‌بری شده‌است.


وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0

برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
public abstract class Product
{
  public string Name { get; set; }
  public abstract ProductOrder Order(int quantity);
}

public class Book : Product
{
  public string ISBN { get; set; }
  public override ProductOrder Order(int quantity) =>
new BookOrder { Quantity = quantity, Product = this };
}

public class ProductOrder
{
  public int Quantity { get; set; }
}

public class BookOrder : ProductOrder
{
  public Book Product { get; set; }
}
در اینجا یک کلاس abstract و پایه‌ی Product وجود دارد که می‌توان متد abstract سفارش دهی آن‌را در کلاس‌های مشتق شده‌ی از آن، مانند Book، بازنویسی کرد.
همانطور که مشاهده می‌کنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده می‌توان درنظر گرفت، همانی است که در کلاس پایه‌ی Product تعریف شده‌است و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفاده‌ی از این کلاس‌ها، تبدیل خروجی متد Order، به BookOrder ضروری است:
var book = new Book
{
  Name = "My book",
  ISBN = "11-1-12-22-0"
};
BookOrder orderBook = (BookOrder)book.Order(1);


امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0

در C# 9.0 با مجاز اعلام شدن خروجی‌های covariant، می‌توان تغییرات زیر را به کدهای فوق اعمال کرد:
public class Book : Product
{
  public string ISBN { get; set; }
  public override BookOrder Order(int quantity) =>
      new BookOrder { Quantity = quantity, Product = this };
}
چون کلاس BookOrder از ProductOrder تعریف شده‌ی در کلاس پایه مشتق شده‌است (مفهوم covariant بودن خروجی متد)، می‌توان در C# 9.0 آن‌را به عنوان خروجی متد Order بازنویسی شده‌ی در کلاس Book، تنظیم کرد.
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائه‌ی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازی‌ها منتفی می‌کند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را می‌توان به صورت زیر خلاصه کرد:
BookOrder orderBook = book.Order(1);
مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
با تعدادی از کامپوننت‌های Bootstrap در مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» آشنا شدید. یکی دیگر از این افزونه‌ها، Typeahead نام دارد که در حقیقت نوعی Autocomplete text box است. در ادامه قصد داریم نحوه استفاده از آن‌را در ASP.NET MVC بررسی کنیم.

استفاده‌ی استاتیک از افزونه Typeahead

منظور از استفاده‌ی استاتیک، مشخص بودن آرایه عناصر و هچنین درج آن به صورت html encoded در صفحه است. برای این منظور، کنترلر برنامه چنین شکلی را خواهد داشت:
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
            return View();
        }
    }
}
در اینجا یک آرایه با تعداد عناصر مشخص، تبدیل به رشته JSON معادل آن شده و توسط ViewBag.JsonString به View ارسال می‌شود.
View متناظر با آن به نحو ذیل با مشخص سازی نوع data-provide (تا به کتابخانه‌ی جاوا اسکریپتی همراه bootstrap اعلام کند از چه افزونه‌ای در اینجا قرار است استفاده شود)، منبع داده data-source و حداکثر تعداد آیتم ظاهر شونده data-items، می‌تواند طراحی شود:
@{
    ViewBag.Title = "Index";    
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8,
                                  data_source = @ViewBag.JsonString
                              })

به این ترتیب، یک چنین خروجی در صفحه درج می‌شود:
<input autocomplete="off" data-items="8" data-provide="typeahead" 
data-source="[&quot;Afghanistan&quot;,&quot;Albania&quot;,&quot;Algeria&quot;,&quot;American Samoa&quot;,&quot;Andorra&quot;,&quot;Angola&quot;,&quot;Anguilla&quot;,&quot;Antarctica&quot;,&quot;Antigua and/or Barbuda&quot;]" 
id="search" name="search" type="text" value="" />
همانطور که ملاحظه می‌کنید دقیقا data-source تهیه شده مطابق نیاز خاص این افزونه، html encoded است. به علاوه هر جایی در htmlAttributes صفحه از under line استفاده شده، در این سمت به صورت خودکار به - ترجمه گردیده است.
اگر هم بخواهیم برای آن یک Html Helper درست کنیم، می‌توان به نحو ذیل عمل کرد:
        public static MvcHtmlString TypeaheadFor<TModel, TValue>(
                this HtmlHelper<TModel> htmlHelper,
                Expression<Func<TModel, TValue>> expression,
                IEnumerable<string> source,
                int items = 8)
        {
            var jsonString = new JavaScriptSerializer().Serialize(source);
            return htmlHelper.TextBoxFor(
                expression,
                new
                {
                    autocomplete = "off",
                    data_provide = "typeahead",
                    data_items = items,
                    data_source = jsonString
                }
            );
        }


استفاده پویا و Ajax ایی از افزونه Typeahead

اگر بخواهیم data-source را به صورت پویا، هربار از بانک اطلاعاتی دریافت و ارائه دهیم، نیاز به کمی اسکریپت نویسی خواهد بود:
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public JsonResult GetNames(string term)
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };

            var results = array.Where(n =>
                n.StartsWith(term, StringComparison.OrdinalIgnoreCase));

            return Json(results.ToArray(), JsonRequestBehavior.AllowGet);
        }
    }
}
در این حالت، کدهای اکشن متدی که یک عبارت، یا قسمتی از آن را از طریق پارامتر term دریافت و خروجی JSON مناسبی را ارائه می‌کند، همانند متد GetNames فوق خواهد بود.
سپس در تعاریف View، قسمت data-source مرتبط با TextBox حذف و از طریق فراخوانی مستقیم کدهای افزونه typeahead مقدار دهی می‌گردد:
@{
    ViewBag.Title = "Index";
    var url = Url.Action("GetNames", "Home");
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8
                              })
@section JavaScript
{
    <script type="text/javascript">
            $(function () {
                $('#search').typeahead({
                    source: function (term, process) {
                        return $.getJSON('@url', { term: term }, function (data) { return process(data); });
                    }
                });
            });
    </script>
}
در اینجا توسط متد getJSON کتابخانه jQuery، مقدار عبارت وارد شده در TextBox جستجو، به آدرس اکشن متد GetNames ارسال و سپس حاصل به source افزونه typeahead انتساب داده می‌شود.
مطالب
استفاده از FluentValidation در ASP.NET MVC
برای هماهنگی این کتابخانه با ASP.NET MVC نیاز به نصب FluentValidation.Mvc3 یا FluentValidation.Mvc4 از طریق Nuget یا دانلود کتابخانه از سایت CodePlex می‌باشد. بعد از نصب کتابخانه، نیاز به تنظیم FluentValidationModelValidatorProvider داخل متد Application_Start (فایل Global.asax) داریم: 

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    FluentValidationModelValidatorProvider.Configure();
}
تصور کنید دو کلاس Person و PersonValidator را به صورت زیر داریم:
[Validator(typeof(PersonValidator))]
    public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}
 
public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        RuleFor(x => x.Id).NotNull();
        RuleFor(x => x.Name).Length(0, 10);
        RuleFor(x => x.Email).EmailAddress();
        RuleFor(x => x.Age).InclusiveBetween(18, 60);
    }
}
همان طور که ملاحظه می‌کنید، در بالای تعریف کلاس Person با استفاده از ValidatorAttribute مشخص کرده ایم که از PersonValidator جهت اعتبارسنجی استفاده کند.
در آخر می‌توانیم Controller و View ی برنامه مان را درست کنیم:
public class PeopleController : Controller {
    public ActionResult Create() {
        return View();
    }
 
    [HttpPost]
    public ActionResult Create(Person person) {
 
        if(! ModelState.IsValid) { // re-render the view when validation failed.
            return View("Create", person);
        }
 
        TempData["notice"] = "Person successfully created";
        return RedirectToAction("Index");
    }
}
@Html.ValidationSummary()
 
@using (Html.BeginForm()) {
Id: @Html.TextBoxFor(x => x.Id) @Html.ValidationMessageFor(x => x.Id)
<br />
Name: @Html.TextBoxFor(x => x.Name) @Html.ValidationMessageFor(x => x.Name) 
<br />
Email: @Html.TextBoxFor(x => x.Email) @Html.ValidationMessageFor(x => x.Email)
<br />
Age: @Html.TextBoxFor(x => x.Age) @Html.ValidationMessageFor(x => x.Age)
 
<input type="submit" value="submit" />
}
اکنون DefaultModelBinder موجود در MVC برای اعتبارسنجی شیء Person از FluentValidationModelValidatorProvider استفاده خواهد کرد.
توجه داشته باشید که FluentValidation با اعتبارسنجی سمت کاربر ASP.NET MVC به خوبی کار خواهد کرد منتها نه برای تمامی اعتبارسنجی ها. به عنوان مثال تمام قوانینی که از شرط‌های When/Unless استفاده کرده اند، Validator‌های سفارشی، و قوانینی که در آن‌ها از Must استفاده شده باشد، اعتبارسنجی سمت کاربر نخواهند داشت. در زیر لیست Validator هایی که با اعتبارسنجی سمت کاربر به خوبی کار خواهند کرد آمده است:
  • NotNull/NotEmpty
  • Matches 
  • InclusiveBetween 
  • CreditCard 
  • Email 
  • EqualTo 
  • Length 
صفت CustomizeValidator
با استفاده از CustomizeValidatorAttribute می‌توان نحوه اجرای Validator را تنظیم کرد. به عنوان مثال اگر میخواهید که Validator تنها برای یک RuleSet مخصوص انجام شود می‌توانید مانند زیر عمل کنید: 
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
  // ...
}


مواردی که تا اینجا گفته شد برای استفاده در یک برنامه‌ی ساده MVC کافی به نظر می‌رسد، اما از اینجا به بعد مربوط به مواقعی است که نخواهیم از Attribute‌ها استفاده کنیم و کار را به IoC بسپاریم. 
استفاده از Validator Factory با استفاده از یک IoC Container
Validator Factory چیست؟ Validator Factory یک کلاس می‌باشد که وظیفه ساخت نمونه از Validator‌‌ها را بر عهده دارد. اینترفیس IValidatorFactory به صورت زیر می‌باشد:  
public interface IValidatorFactory {
   IValidator<T> GetValidator<T>();
   IValidator GetValidator(Type type);
}
ساخت Validator Factory سفارشی:
برای ساخت یک Validator Factory شما می‌توانید به طور مستقیم IValidatorFactory را پیاده سازی نمایید یا از کلاس ValidatorFactoryBase به عنوان کلاس پایه استفاده کنید (که مقداری از کارها را برای شما انجام داده است). در این مثال نحوه ایجاد یک Validator Factory که از StructureMap استفاده می‌کند را بررسی خواهیم کرد. ابتدا نیاز به ثبت Validator‌ها در StructureMap داریم: 
ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
 
public class MyRegistry : Registry {
    public MyRegistry() {
        For<IValidator<Person>>()
    .Singleton()
    .Use<PersonValidator>();
    }
}
در اینجا StructureMap را طوری تنظیم کرده ایم که از یک Registry سفارشی استفاده کند. در داخل این Registry به StructureMap میگوییم که زمانی که خواسته شد تا یک نمونه از IValidator<Person> ایجاد کند، PersonValidator را بر گرداند. متد CreateInstance نوع مناسب را نمونه سازی می‌کند (CustomerValidator) و آن را بازمی گرداند ( یا Null بر می‌گرداند اگر نوع مناسبی وجود نداشته باشد) 
استفاده از AssemblyScanner 
FluentValidation دارای یک AssemblyScanner می‌باشد که کار ثبت Validator‌ها داخل یک اسمبلی را راحت‌تر می‌سازد. با استفاده از AssemblyScanner کلاس MyRegistery ما شبیه قطعه کد زیر خواهد شد: 
public class MyRegistry : Registry {
   public MyRegistry() {
 
     AssemblyScanner.FindValidatorsInAssemblyContaining<MyValidator>()
       .ForEach(result => {
            For(result.InterfaceType)
               .Singleton()
               .Use(result.ValidatorType);
       });
   }
}
حالا زمان استفاده از factory ساخته شده در متد Application_Start برنامه می‌باشد:
protected void Application_Start() {
    RegisterRoutes(RouteTable.Routes);
 
    //Configure structuremap
    ObjectFactory.Configure(cfg => cfg.AddRegistry(new MyRegistry()));
    ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
 
    //Configure FV to use StructureMap
    var factory = new StructureMapValidatorFactory();
 
    //Tell MVC to use FV for validation
    ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(factory));        
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}
اکنون FluentValidation از StructureMap برای نمونه سازی Validatorها استفاده خواهد کرد و کار اعتبارسنجی مدل‌ها به FluentValidaion سپرده شده است.
مطالب
سفارشی سازی عناصر صفحات پویای افزودن و ویرایش رکوردهای jqGrid در ASP.NET MVC
پیشنیاز این بحث مطالعه‌ی مطالب «صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC» و «فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC» است و در اینجا جهت کوتاه شدن بحث، صرفا به تغییرات مورد نیاز جهت اعمال بر روی مثال‌ها اکتفاء خواهد شد.


صورت مساله

    public class Product
    {
        public int Id { set; get; }
        public DateTime AddDate { set; get; }
        public string Name { set; get; }
        public decimal Price { set; get; }
    }
در اینجا تعریف محصول، شامل خاصیت‌های تاریخ ثبت، نام و قیمت آن است.
می‌خواهیم زمانیکه فرم‌های پویای ویرایش یا افزودن رکوردها ظاهر شدند، در حین تکمیل نام، یک auto complete ظاهر شود:


در حین ورود تاریخ، یک date picker شمسی جهت سهولت ورود اطلاعات نمایش داده شود:


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



پیشنیازها

- برای نمایش auto complete از همان امکانات توکار jQuery UI که به همراه jqGrid عرضه می‌شوند، استفاده خواهیم کرد.
- برای نمایش date picker شمسی از مطلب «PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند» کمک خواهیم گرفت.
- جهت اعمال خودکار حرف سه رقم جدا کننده هزارها از افزونه‌ی Price Format جی‌کوئری استفاده می‌کنیم.

تعریف و الحاق این پیشنیازها، فایل layout برنامه را به شکل زیر تغییر خواهد داد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>

    <link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
    <link href="~/Content/jquery.jqGrid/ui.jqgrid.css" rel="stylesheet" />
    <link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div>
        @RenderBody()
    </div>

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

    @RenderSection("Scripts", required: false)
</body>
</html>


تغییرات مورد نیاز سمت کلاینت، جهت اعمال افزونه‌های جی‌کوئری و سفارشی سازی عناصر دریافت اطلاعات

الف) نمایش auto complete در حین ورود نام محصولات
                colModel: [
                    {
                        name: 'Name', index: 'Name', align: 'right', width: 100,
                        editable: true, edittype: 'text',
                        editoptions: {
                            maxlength: 40,
                            dataInit: function (elem) {
                                // http://jqueryui.com/autocomplete/
                                $(elem).autocomplete({
                                    source: '@Url.Action("GetProductNames","Home")',
                                    minLength: 2,
                                    select: function (event, ui) {
                                        $(elem).val(ui.item.value);
                                        $(elem).trigger('change');
                                    }
                                });
                            }
                        },
                        editrules: {
                            required: true
                        }
                    }           
     ],
برای اعمال هر نوع افزونه‌ی جی‌کوئری به عناصر فرم‌های خودکار ورود اطلاعات در jqGrid، تنها کافی است که رویداد dataInit یک ستون را بازنویسی کنیم. در اینجا توسط elem، المان جاری را در اختیار خواهیم داشت. سپس از این المان جهت اعمال افزونه‌ای دلخواه استفاده می‌کنیم. برای مثال در اینجا از متد autocomplete استفاده شده‌است که جزئی از jQuery UI استاندارد است.
برای پردازش سمت سرور آن و مقدار دهی url آن، یک چنین اکشن متدی را می‌توان تدارک دید:
        public ActionResult GetProductNames(string term)
        {
            var list = ProductDataSource.LatestProducts
                .Where(x => x.Name.StartsWith(term))
                .Select(x => x.Name)
                .Take(10)
                .ToArray();
            return Json(list, JsonRequestBehavior.AllowGet);
        }
مقدار term، عبارتی است که کاربر وارد کرده است. توسط متد StartsWith، کلیه نام‌هایی را که با این عبارت شروع می‌شوند (البته 10 مورد از آن‌ها را) بازگشت می‌دهیم.

ب) نمایش date picker شمسی در حین ورود تاریخ
                colModel: [
                    {
                        name: 'AddDate', index: 'AddDate', align: 'center', width: 100,
                        editable: true, edittype: 'text',
                        editoptions: {
                            maxlength: 10,
                            // https://www.dntips.ir/post/1382
                            onclick: "PersianDatePicker.Show(this,'@today');"
                        },
                        editrules: {
                            required: true
                        }
                    }
                ],
Date picker مورد استفاده، وابستگی خاصی به jQuery ندارد. مطابق مستندات آن باید در رویدادگردان onclick، این تقویم شمسی را فعال کرد. بنابراین در قسمت onclick دقیقا این مورد را اعمال می‌کنیم.

 @{
ViewBag.Title = "Index";
var today = DateTime.Now.ToPersianDate();
}
مقدار today آن در ابتدای View به نحو فوق تعریف شده‌است. کدهای کامل متد کمکی ToPersianDate در پروژه‌ی پیوست موجود است.

ج) اعمال حروف سه رقم جدا کننده هزارها در حین ورود قیمت
                colModel: [
                    {
                        name: 'Price', index: 'Price', align: 'center', width: 100,
                        formatter: 'currency',
                        formatoptions:
                        {
                            decimalSeparator: '.',
                            thousandsSeparator: ',',
                            decimalPlaces: 2,
                            prefix: '$'
                        },
                        editable: true, edittype: 'text',
                        editoptions: {
                            dir: 'ltr',
                            dataInit: function (elem) {
                                // http://jquerypriceformat.com/
                                $(elem).priceFormat({
                                    prefix: '',
                                    thousandsSeparator: ',',
                                    clearPrefix: true,
                                    centsSeparator: '',
                                    centsLimit: 0
                                });
                            }
                        },
                        editrules: {
                            required: true,
                            minValue: 0
                        }
                    }
                ],
افزونه‌ی price format نیز یک افزونه‌ی جی‌کوئری است. بنابراین دقیقا مانند حالت auto complete آن‌را در dataInit فعال سازی می‌کنیم و همچنین یک سری تنظیم ابتدایی مانند مشخص سازی  thousandsSeparator آن‌را مقدار دهی خواهیم کرد.


یک نکته

همین تعاریف را دقیقا به فرم‌های جستجو نیز می‌توان اعمال کرد. در اینجا برای حالات ویرایش و افزودن رکوردها، editoptions مقدار دهی شده‌است؛ در مورد فرم‌های جستجو باید searchoptions و برای مثال dataInit آن‌را مقدار دهی کرد.



مشکل مهم!

با تنظیمات فوق، قسمت UI بدون مشکل کار می‌کند. اما اگر در سمت سرور، مقادیر دریافتی را بررسی کنیم، نه تاریخ و نه قیمت، قابل دریافت نیستند. زیرا تاریخ ارسالی به سرور شمسی است و مدل برنامه DateTime میلادی می‌باشد. همچنین به دلیل وجود حروف سه رقم جدا کننده هزارها، عبارت دریافتی قابل تبدیل به عدد نیستند و مقدار دریافتی صفر خواهد بود.
برای رفع این مشکلات، نیاز به تغییر model binder توکار ASP.NET MVC است. برای تاریخ‌ها از کلاس PersianDateModelBinder می‌توان استفاده کرد. برای اعداد decimal از کلاس ذیل:
using System;
using System.Globalization;
using System.Threading;
using System.Web.Mvc;

namespace jqGrid05.CustomModelBinders
{
    /// <summary>
    /// How to register it in the Application_Start method of Global.asax.cs
    /// ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
    /// </summary>
    public class DecimalBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType == typeof(decimal?))
            {
                return bindDecimal(bindingContext);
            }
            return base.BindModel(controllerContext, bindingContext);
        }

        private static object bindDecimal(ModelBindingContext bindingContext)
        {
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (valueProviderResult == null)
                return null;
            
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
            decimal value;
            var valueAsString = valueProviderResult.AttemptedValue == null ?
                                        null : valueProviderResult.AttemptedValue.Trim();
            if (string.IsNullOrEmpty(valueAsString))
                return null;
            
            if (!decimal.TryParse(valueAsString, NumberStyles.Any, Thread.CurrentThread.CurrentCulture, out value))
            {
                const string error ="عدد وارد شده معتبر نیست";
                var ex = new InvalidOperationException(error, new Exception(error, new FormatException(error)));
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex);
                return null;
            }
            return value;
        }
    }
}
در اینجا عبارت ارسالی به سرور به صورت یک رشته دریافت شده و سپس تبدیل به یک عدد decaimal می‌شود. در آخر به سیستم model binding بازگشت داده خواهد شد. به این ترتیب دیگر مشکلی با پردازش حروف سه رقم جدا کننده هزارها نخواهد بود.

برای ثبت و معرفی این کلاس‌ها باید به نحو ذیل در فایل global.asax.cs برنامه عمل کرد:
using System;
using System.Web.Mvc;
using System.Web.Routing;
using jqGrid05.CustomModelBinders;

namespace jqGrid05
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ModelBinders.Binders.Add(typeof(DateTime), new PersianDateModelBinder());
            ModelBinders.Binders.Add(typeof(decimal), new DecimalBinder());
        }
    }
}


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید
jqGrid05.zip
 
مطالب
C# 12.0 - Using aliases for any type
دات‌نت 8 به همراه بهبودهای قابل ملاحظه‌ای در کارآیی برنامه‌های دات‌نتی است و در این بین تعدادی قابلیت جدید را نیز به زبان سی‌شارپ اضافه کرده‌است. در این مطلب ویژگی جدید «Alias any type» آن‌را بررسی می‌کنیم. پیشنیاز کار با این قابلیت تنها نصب SDK دات‌نت 8 است.


امکان تعریف alias، قابلیت جدیدی نیست!

در نگارش‌های پیشین زبان #C نیز می‌توان برای نوع‌های نام‌دار دات‌نت، alias/«نام مستعار» تعریف کرد؛ برای مثال:
using MyConsole = System.Console;

MyConsole.WriteLine("Test console");
Aliasها در قسمت using تعاریف یک کلاس معرفی می‌شوند و یکی از اهدف آن‌ها، کوتاه کردن تعاریف فضاهای نام طولانی است و یا رفع تداخل‌ها؛ همچنین تنها به Named types، محدود هستند و Named types فقط شامل این موارد می‌شوند: classes ،delegates ،interfaces ،records و structs

بنابراین دو حالت تعریف Namespace alias برای کوتاه سازی فضاهای نام طولانی و یا تعریف Type alias برای معرفی یک نام مستعار جدید برای نوعی مشخص، میسر است:
// Namespace alias
using SuperJSON = System.Text.Json;
var document = SuperJSON.JsonSerializer.Serialize("{}");

// Type alias
using SuperJSON = System.Text.Json.JsonSerializer;
var document = SuperJSON.Serialize("{}");

تنها کارکرد نام‌های مستعار، کوتاه و زیبا سازی نام‌های طولانی نیستند. برای مثال گاهی از اوقات ممکن است که بین نام نوع‌های موجود در usingهای جاری، تداخل حاصل شود و برنامه کامپایل نشود. برای مثال فرض کنید که دو using زیر را تعریف کرده‌اید:
using UnityEngine;
using System;

Random rnd = new Random();
کامپایل این برنامه میسر نیست. چون هر دو نوع System.Random و UnityEngine.Random پیشتر تعریف شده‌اند و در اینجا دقیقا مشخص نیست که تامین کننده‌ی شیء Random، کدام فضای نام است. در این حالت می‌توان برای مثال در حین نمونه سازی، فضای نام را صراحتا ذکر کرد:
var rnd = new System.Random();
و یا می‌توان برای آن نام مستعاری نیز تعریف کرد:
using Random = System.Random;
در این حالت دیگر تداخلی وجود نداشته و کامپایلر دقیقا می‌داند که تامین کننده‌ی Random، کدام کتابخانه و کدام فضای نام است.


امکان تعریف alias برای هر نوعی در C# 12.0

محدودیت امکان تعریف alias برای نوع‌های نام‌دار دات‌نت در C# 12.0 برطرف شده و اکنون می‌توان برای انواع و اقسام نوع‌ها مانند آرایه‌ها، tuples و غیره نیز alias تعریف کرد:
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
using Grade= decimal;
using Point3D = (int, int, int);
using Person = (string name, int age, string country);
using unsafe P = char*;
using Matrix = int[][];

Matrix aMatrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
در اینجا امکان تعریف alias را برای آرایه‌ها، nullable value types، نوع‌های توکار، tupleها و حتی نوع‌های unsafe، مشاهده می‌کنید.
یک نکته: امکان تعریف alias برای nullable reverence types وجود ندارد.


بررسی یک مثال C# 12.0

در اینجا محتویات یک فایل Program.cs یک برنامه‌ی کنسول دات‌نت 8 را مشاهده می‌کنید:
using MyConsole = System.Console;
using Person = (string name, int age, string country);

Person person = new("User 1", 33, "Iran");
Console.WriteLine(person);
PrintPerson(person);

MyConsole.WriteLine("Test console");

static void PrintPerson(Person person)
{
   MyConsole.WriteLine($"{person.name}, {person.age}, {person.country}");
}
در این مثال برای یک نوع tuple سفارشی، یک alias به نام Person تعریف شده و سپس از آن برای نمونه سازی یک شیء جدید و یا ارسال آن به عنوان یک پارامتر متد، استفاده شده‌است. خروجی برنامه‌ی فوق به صورت زیر است:
(User 1, 33, Iran)
User 1, 33, Iran
Test console


چه زمانی بهتر است از قابلیت تعریف نام‌های مستعار نوع‌ها و یا فضاهای نام استفاده شود؟

اگر یک نام طولانی را بتوان به این صورت خلاصه کرد، مفید هستند؛ برای مثال ساده سازی تعریف یک لیست طولانی به صورت زیر:
using Companies = System.Collections.Generic.List<Company>;

Companies GetCompanies()
{
   // logic here
}

class Company
{
   public string Name;
   public int Id;
}
و مثالی دیگر در این زمینه، کوتاه سازی تعاریف متداول جنریک طولانی است:
using EventHandlers = System.Collections.Generic.IEnumerable<System.Func<System.Threading.Tasks.Task>>;

 و یا اگر بتوانند رفع تداخلی را حاصل کنند، بکارگیری آن‌ها ضروری است (مانند مثال شیء Random ابتدای بحث) و یا اگر بتوانند از تکرار تعریف یک tuple جلوگیری کنند، ذکر آن‌ها یک refactoring مثبت به‌شمار می‌رود؛ مانند مثال زیر که در آن از تعریف نوع tuple ای، دوبار استفاده شده‌است:
using Country = (string Abbreviation, string Name);

Country GetCountry(string abbreviation)
{
   // Logic here
}

List<Country> GetCountries()
{
   // Logic here
}
 اما ... آیا واقعا تعاریفی مانند ذیل، مفید یا ضروری هستند؟
using Ints = int[];
using DatabaseInt = int?;
using OptionalFloat = float?;
اینجا فقط قطعه کدی اضافی را که بیشتر سبب سردرگمی و بالا بردن درجه‌ی پیچیدگی برنامه می‌شود، تولید کرده‌ایم. خوانایی و سادگی درک برنامه در این حالت کاهش پیدا می‌کند.


میدان دید نام‌های مستعار

به صورت پیش‌فرض، تمام نام‌های مستعار تنها در داخل همان فایلی که تعریف شده‌اند، قابل استفاده می‌باشند. از زمان C# 10.0 ، می‌توان پیش از واژه‌ی کلیدی using از واژه‌ی کلیدی global نیز استفاده کرد تا تعریف آن‌ها فقط در پروژه‌ی جاری به صورت سراسری قابل دسترسی شود.
به همین جهت اگر نوعی قرار است در سایر پروژه‌ها استفاده شود، بهتر است از global using استفاده نشده و از همان روش‌های متداول تعریف records و یا classes استفاده شود.
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
سلام ، با دیباگ کردن فهمیدم بعد از لاگین در قسمت زیر Claim را null برمیگردونه
        public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
        {
            var identity = currentPrincipal.Identity as ClaimsIdentity;
            if (identity == null)
                return null;

            var claim = identity.Claims.FirstOrDefault(c => c.Type == key);
            return claim?.Value;
        }
که با برداشتن breakpoint قسمت CreateUserIdentityAsync در ApplicationSignInManager
        public override Task<ClaimsIdentity> CreateUserIdentityAsync(User user)
        {
            return _userManager.GenerateUserIdentityAsync(user);
        }
و استفاده در AccountController در اکشن login قسمت post بصورت زیر مشکل حل شد
                case SignInStatus.Success:
                    var user = await _userManager.FindByNameAsync(model.UserName);
                    await _signInManager.CreateUserIdentityAsync(user);
                    return RedirectToLocal(returnUrl);