مطالب
آشنایی با WPF قسمت پنجم : DataContext بخش دوم
در ادامه قسمت قبلی قصد داریم دو کنترل دیگر را نیز بایند کنیم؛ ولی از آنجا که مقادیر آن‌ها رشته‌ای یا عددی نیست و مقداری متفاوت هست، از مبحثی به نام ValueConverter استفاده خواهیم کرد.
Value Converter چیست؟


موقعی که شما قصد بایند کردن دو نوع داده متفاوت را به هم دارید، نیاز به یک کد واسط پیدا می‌کنید تا این کد واسط مقادیر شما را از مبدا دریافت کرده و تبدیل به نوعی کند که مقصد بتواند از آن استفاده کند یا بلعکس. ValueConverter نام کلاسی است که از یک اینترفیس به نام IValueConverter ارث بری کرده است و شامل دو متد تبدیل نوع از مبدا به مقصد Convert و دیگری از مقصد به مبدا  ConvertBack می‌شود که خیلی کمتر پیاده سازی می‌شود.
پیاده سازی یک کلاس مبدل سه مرحله دارد:
  • مرحله اول :ساخت کلاس ValueConverter
  • مرحله دوم : تعریف آن به عنوان یک منبع یا ریسورس
  • مرحله سوم : استفاده از آن در عملیات بایند کردن Binding

در مرحله اول، نحوه پیاده سازی کلاس ValueConverter به شکل زیر است:
public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        // Do the conversion from bool to visibility
    }
 
    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        // Do the conversion from visibility to bool
    }
}

در متد تبدیل باید مقداری را که کنترل نیاز دارد، بر اساس مقادیر کلاس، ایجاد و بازگشت دهید.

در مرحله‌ی دوم نحوه تعریف مبدل ما در پنجره XAML به صورت زیر می‌باشد:
<Window x:Class="test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:نامی دلخواه="clr-namespace:فضای نامی که کلاس مبدل در آن قرار دارد"
        >

    <Window.Resources>
        <نام دلخواهی که در بالا تعریف کرده اید: ClassNameنام کلاس  x:Key="کلید این آیتم در ریسورس"/>
    </Window.Resources>

کلمه‌های کلیدی xmlns که مخفف XML NameSpace هستند جهت تعریف دسترسی به فضاهای نام طراحی شده‌اند و طبق تعریف مایکروسافت همان مفهوم تگ‌های تعریف فضای نام در XML را دارند که توسط مایکروسافت توسعه یافته‌اند. این تعریف‌ها در تگ ریشه (در اینجا window) تعریف می‌شوند. دو فضای نام اولی که به طور پیش فرض در همه جای پروژه قرار دارند، اشاره به فریم ورک WPF دارند. کلمه‌ی کلیدی x در خط شماره سه، نام دلخواهی است که دسترسی ما را به خصوصیات یا تعاریف XAML موجود در sdk باز می‌کند؛ مثلا استفاده از خصوصیاتی چون x:key یا x:class را به همراه دارد.
پس الان باید خط چهارم برای ما روشن باشد؛ فضای نام جدیدی را در برنامه خودمان ایجاد کرده‌ایم که این تگ به آن اشاره می‌کند و نام دلخواهی هم برای اشاره به این فضای نام برایش در نظر گرفته‌ایم. هر موقع در برنامه این نام دلخواه تعیین شده قرار گیرد، یعنی اشاره به این فضای نام که در قسمت Window.resource خط هشتم تعریف شده است.
در خط هشتم، یک ریسورس (منبع) را به برنامه معرفی کرده‌ایم:
ریسورس‌ها برای ذخیره سازی داده‌ها در سطح یک کنترل، سطح محلی در یک پنجره، یا سطح عمومی در کل پروژه به کار می‌روند. محدودیتی در ذخیره داده‌ها وجود ندارد و هر چقدر که دوست دارید می‌توانید داده به آن پاس کنید. این داده‌ها می‌توانند یک سری اطلاعات ذخیره شده در یک ساختار ساده تا یک ساختار سلسه مراتبی از کنترل‌ها باشند. ریسورس‌ها به شما این اجازه را می‌دهند تا داده‌ها را در یک مکان ذخیره کرده و آن‌ها را در یک یا چندجا مورد استفاده قرار دهید.

از آن جا که مباحث ریسورس‌ها را در یک مقاله‌ی جداگانه بررسی می‌کنیم، فقط به ذکر نکات بالا جهت کد فعلی بسنده خواهیم کرد و ادامه‌ی آن را در یک مقاله دیگر مورد بررسی قرار می‌دهیم.
هر ریسورس دارای یک نام یا یک کلید است که با خصوصیت x:key تعریف می‌شود.
ریسورس بالا یک کلاس را که در فضای نام دلخواهی قرار دارد، تعریف می‌کند و یک کلید هم به آن انتساب می‌دهد.
مرحله‌ی سوم معرفی ریسورس به عملیات Binding است:
{Binding نام پراپرتی کلاس, Converter={StaticResource کلید آیتم مربوطه در ریسورس}, ConverterParameter=پارامتری که به کلاس مبدل پاس می‌شود}

بخش اول دقیقا همان چیزی است که در قسمت قبلی یاد گرفتیم. معرفی پراپرتی که باید عمل بایند به آن صورت گیرد. قسمت بعدی معرفی مبدل است و از آن جا که تابع مبدل ما در یک منبع است، اینگونه می‌نویسیم: {} را باز کرده و ابتدا کلمه StaticResource فاصله و سپس کلید ریسورس که تابع را از ریسورس فراخوانی کند و قسمت بعدی هم پاس کردن یک پارامتر به تابع مبدل است.

حال که با اصول نوشتار آشنا شدیم کار را آغاز می‌کنیم.
قصد داریم یک مبدل برای فیلد جنیست درست کنیم. از آنجا که این فیلد Boolean است و خصوصیت IsChecked یک RadioButton هم Boolean است، می‌توان یک ارتباط مستقیم را ایجاد کرد. ولی مشکل در اینجا هست که True برای مذکر است و false برای مؤنث. در نتیجه تنها Radiobutton مربوطه به جنس مذکر به این حالت پاسخ می‌دهد و از آنجا که  برای جنس مونث  false در نظر گرفته شده است، انتخاب آن هم false خواهد بود. پس باید در مبدل، مقداری که کنترل می‌خواهد را شناسایی کرده و اگر مقدار با آن برابر بود، True را بازگردانیم. مقداری که هر کنترل درخواست می‌کند را از طریق پارامتر به تابع مبدل ارسال می‌کنیم. radiobutton مذکر، مقدار True را به عنوان پارامتر ارسال می‌کند و radiobutton مونث هم مقدار false را به عنوان پارامتر ارسال می‌کند. اگر تابع مبدل را ببینید، این مقدار‌ها با پارامترها همخوانی دارند، True در غیر این صورت false بر میگرداند.

مرحله اول تعریف کلاس ValueConveter:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace test.ValueConverters
{
    public class GenderConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var ParameterString = parameter as string;
            if (ParameterString == null)
                return DependencyProperty.UnsetValue;

            bool bparam;

            bool test = bool.TryParse(parameter.ToString(), out bparam);
            if (test)
            {
                return ((bool)value).Equals(bparam);
            }
            return DependencyProperty.UnsetValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
در کد بالا پارامتر ارسالی را دریافت می‌کنیم و اگر برابر با Null باشد، مقداری برگشتی را عدم ذکر شدن خصوصیت وابسته اعلام می‌کنیم. در نتیجه انگار این خصوصیت مقداردهی نشده است. اگر مخالف Null باشد، کار ادامه پیدا می‌کند. در نهایت مقایسه‌ای بین پارامتر و مقدار پراپرتی (value) صورت گرفته و نتیجه‌ی مقایسه را برگشت می‌دهیم.

برای تعریف این مبدل در محیط XAML به صورت زیر اقدام می‌کنیم:
<Window x:Class="test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:valueConverters="clr-namespace:test.ValueConverters"
        Title="MainWindow" Height="511.851" Width="525">

    <Window.Resources>
        <valueConverters:GenderConverter x:Key="GenderConverter"/>
    </Window.Resources>
</window>
 کلمه valueConveter به فضای نام test.valueConverters اشاره می‌کند که در قسمت ریسورس، از کلاس GenderConverter آن استفاده می‌کنیم و کلیدی که برای این ریسورس در نظر گرفتیم را GenderConverter تعریف کردیم (هم نامی کلید ریسورس و نام کلاس مبدل اتفاقی است و ارتباطی با یکدیگر ندارند).

نکته: در صورتی که بعد از تعریف ریسورس با خطای زیر روبرو شدید و محیط طراحی Design را از دست دادید یکبار پروژه را بیلد کنید تا مشکل حل شود.
The name "GenderConverter" does not exist in the namespace "clr-namespace:test.ValueConverters".


اکنون در عملیات بایندینگ دو کنترل اینگونه می‌نویسیم:
     <RadioButton GroupName="Gender" IsChecked="{Binding Gender, Converter={StaticResource GenderConverter}, ConverterParameter=True}" Name="RdoMale"  >Male</RadioButton>
                <RadioButton GroupName="Gender" IsChecked="{Binding Gender, Converter={StaticResource GenderConverter}, ConverterParameter=False}" Name="RdoFemale" Margin="0 5 0 0"  >Female</RadioButton>
حال برنامه را اجرا کرده و نتیجه را ببینید. برای تست بهتر می‌توانید جنسیت فرد را در منبع داده تغییر دهید. همچنین از آنجا که این مقدار برای جنس مذکر نیازی به بررسی و تبدیل ندارد می‌توانید عملیات بایند را عادی بنویسید:
<RadioButton GroupName="Gender" IsChecked="{Binding Gender}" Name="RdoMale"  >Male</RadioButton>
این کد می‌تواند برای تمامی وضعیت‌های دو مقداری چون جنسیت و وضعیت تاهل و ... هم به کار برود. ولی حالا سناریویی را تصور کنید که که مقادیر از دو تا بیشتر شود؛ مثل وضعیت تحصیلی ، دسترسی‌ها و غیره که این‌ها نیاز به داده‌های شمارشی چون Enum‌ها دارند. روند کار دقیقا مانند بالاست:
هر کنترل مقداری از یک enum را میپذیرد که میتواند آن مقدار را با استفاده از پارامتر، به تابع مبدل ارسال کند و سپس تابع چک می‌کند که آیا چنین مقداری در enum مدنظر یافت می‌شود یا خیر؛ این کد الان کار می‌کندو واقعا هم کد درستی است و هیچ مشکلی هم ندارد. ولی برای یک لحظه تصور کنید پنجره شما شامل چهار خصوصیت enum دار است. یعنی الان باید 4 تابع مبدل بنویسید. پس باید کد را بازنویسی کنیم تا به هر enum که مدنظر است پاسخ دهد؛ به این ترتیب تنها یک تابع مبدل می‌نویسیم.

جهت یادآوری نگاهی به کلاس برنامه می‌اندازیم:
public enum FieldOfWork
    {
        Actor=0,
        Director=1,
        Producer=2
    }
    public class Person : INotifyPropertyChanged
    { 
        public bool Gender { get; set; }

        public string ImageName { get; set; }

        public string Country { get; set; }

        public DateTime Date { get; set; }

        public FieldOfWork FieldOfWork { get; set; }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged();
            }
        }
فعلا IList را از پراپرتی FieldOfWork بر میداریم تا این سناریو باشد که تنها یک Enum قابل انتخاب است:
public FieldOfWork FieldOfWork { get; set; }
بعدا حالت قبلی را بررسی میکنیم.

کد کلاس مبدل را به صورت زیر می‌نویسیم:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace test.ValueConverters
{
    public class EnumConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var ParameterString = parameter as string;
            if (ParameterString == null)
                return DependencyProperty.UnsetValue;

            if (Enum.IsDefined(value.GetType(), value) == false)
                return DependencyProperty.UnsetValue;

            object paramvalue = Enum.Parse(value.GetType(), ParameterString);
            return paramvalue.Equals(value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
در مرحله اول مثل کد قبلی بررسی می‌شود که آیا پارامتری ارسال شده است یا خیر. در مرحله دوم بررسی می‌شود که نوع داده مقدار پراپرتی چیست یعنی چه Enum ایی مورد استفاده قرار گرفته است. اگر در Enum مقداری که در پارامتر به آن ذکر شده است وجود نداشته باشد، بهتر هست که کار در همین جا به پایان برسد؛ زیرا که یک پارامتر اشتباهی ارسال شده است و چنین مقداری در Enum وجود ندارد. در غیر اینصورت کار ادامه می‌یابد و پارامتر را به enum تبدیل کرده و با مقدار مقایسه می‌کنیم. اگر برابر باشند نتیجه true را باز میگردانیم.

کد قسمت ریسورس را با کلاس جدید به روز می‌کنیم:
    <Window.Resources>
        <valueConverters:GenderConverter x:Key="GenderConverter"/>
        <valueConverters:EnumConverter x:Key="EnumConverter"></valueConverters:EnumConverter>
    </Window.Resources>
کد چک باکس‌ها هم به شکل زیر تغییر می‌یابد:
    <CheckBox Name="ChkActor" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumConverter}, ConverterParameter=Actor}" >Actor/Actress</CheckBox>
                <CheckBox Name="ChkDirector" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumConverter}, ConverterParameter=Director}" >Director</CheckBox>
                <CheckBox Name="ChkProducer" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumConverter}, ConverterParameter=Producer}" >Producer</CheckBox>
کلاس GetPerson که منبع داده ما را فراهم می‌کند هم به شکل زیر است:
  public static Person GetPerson()
        {
            return new Person()
            {
                Name = "Leo",
                Gender =true,
                ImageName ="man.jpg",
                Country = "Italy",
                FieldOfWork = test.FieldOfWork.Actor,
                Date = DateTime.Now.AddDays(-3)
            };
        }

برنامه را اجرا کنید تا نتیجه کار را ببینید. باید چک باکس Actor تیک خورده باشد. میتوانید منبع داده را تغییر داده تا نتیجه کار را ببینید.

بگذارید فیلد FieldOfWork را به حالت قبلی یعنی IList برگردانیم. در بسیاری از اوقات ما چند گزینه از یک Enum را انتخاب می‌کنیم، مثل داشتن چند سطح دسترسی یا چند سمت کاری و ...

کلاس را به همراه کد GetPerson به شکل زیر تغییر می‌دهیم:
    public enum FieldOfWork
    {
        Actor=0,
        Director=1,
        Producer=2
    }
    public class Person : INotifyPropertyChanged
    { 
        public bool Gender { get; set; }

        public string ImageName { get; set; }

        public string Country { get; set; }

        public DateTime Date { get; set; }

        public IList<FieldOfWork> FieldOfWork { get; set; }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged();
            }
        }

        public static Person GetPerson()
        {
            return new Person
            {
                Name = "Leo",
                Gender = true,
                ImageName = "man.jpg",
                Country = "Italy",
                FieldOfWork = new FieldOfWork[] { test.FieldOfWork.Actor, test.FieldOfWork.Producer },
                Date = DateTime.Now.AddDays(-3)
            };
        }
}

دو Enum بازیگر و تهیه کننده را انتخاب کرده‌ایم، پس در زمان اجرا باید این دو گزینه انتخاب شوند.
کد مبدل را به صورت زیر می‌نویسیم:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace test.ValueConverters
{
    public class EnumList : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var ParameterString = parameter as string;
            if (ParameterString == null)
                return DependencyProperty.UnsetValue;

         var enumlist= (IList) value;         

            if(enumlist==null && enumlist.Count<1)
                return DependencyProperty.UnsetValue;

            if (Enum.IsDefined(enumlist[0].GetType(), ParameterString) == false)
                return DependencyProperty.UnsetValue;


            /*
              foreach (var item  in enumlist)
            {
                object paramvalue = Enum.Parse(item.GetType(), ParameterString);
                bool result = item.Equals(paramvalue);
                if (result)
                    return true;
            }
            return false;
             */
            return (from object item in enumlist let paramvalue = Enum.Parse(item.GetType(), ParameterString) select item.Equals(paramvalue)).Any(result => result);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
مقدار پراپرتی را به نوع IList تبدیل می‌کنید و اگر لیست معتبر بود، برنامه را ادامه می‌دهیم؛ در غیر اینصورت تابع خاتمه می‌یابد. بعد از اینکه صحت وجود لیست اعلام شد، بررسی میکنیم آیا Enum چنین مقداری که پارامتر ذکر کرده است را دارد یا یک پارامتر اشتباهی است.
در حلقه‌ای که به شکل توضیح درآمده، همه آیتم‌های مربوطه در لیست را بررسی کرده و اگر آیتمی برابر پارامتر باشد، True بر میگرداند و در صورتی که حلقه به اتمام برسد و آیتم پیدا نشود، مقدار False را برمی‌گرداند. این حلقه از آن جهت به شکل توضیح درآمده است که کد Linq آن در زیر نوشته شده است.

تعریف کلاس بالا در ریسورس:
   <Window.Resources>
        <valueConverters:GenderConverter x:Key="GenderConverter"/>
        <valueConverters:EnumConverter x:Key="EnumConverter"></valueConverters:EnumConverter>
        <valueConverters:EnumList x:Key="EnumList"></valueConverters:EnumList>
    </Window.Resources>
و تغییر کلید ریسورس در خطوط چک باکس ها، باقی موارد همانند قبل ثابت هستند:
     <CheckBox Name="ChkActor" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumList}, ConverterParameter=Actor}" >Actor/Actress</CheckBox>
                <CheckBox Name="ChkDirector" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumList}, ConverterParameter=Director}" >Director</CheckBox>
                <CheckBox Name="ChkProducer" IsChecked="{Binding FieldOfWork, Converter={StaticResource EnumList}, ConverterParameter=Producer}" >Producer</CheckBox>

نتیجه‌ی کار باید به شکل زیر باشد:
فیلد جنسیت و زمینه کاری از تابع مبدل به دست آمده است.

  بدیهی است خروجی‌های بالا برای کنترل هایی است که مقدار Boolean را می‌پذیرند و برای سایر کنترل‌ها باید با کمی تغییر در کد و نوع برگشتی که تحویل خروجی متد مبدل می‌شود، دهید.

دانلود فایل‌های این قسمت
مطالب
افزودن فایلهای تولیده شده پویا به صورت Bundle به سیستم Optimization
گاهی از اوقات نیاز است تا از یک محتوای پویا، برای تولید فایلهای CSS و اسکریپت‌های خود استفاده کنید. دلایل زیادی برای اینکار وجود دارند؛ همانند اسکریپت تولید شده در Signalr که بر اساس کلاس hub شما و متدهای پیاده سازی شده‌ی در آن تولید می‌شود. همچنین روش‌های زیادی برای تولید این محتوای پویا وجود دارد که یک نمونه‌ی آن در اینجا ذکر شده است.
قرار دادن این محتوای تولید شده در سیستم Bundling MVC به شکل مستقیم امکان پذیر نیست؛ زیرا این سیستم با فایل‌های استاتیک سر و کار دارد و افزودن یک url به آن مجاز نمی‌باشد. حال اگر در پروژه‌ی خود محتوای پویایی را تولید کرده و می‌خواهید از مزایای فشرده سازی سیستم Bundling بهره‌مند شوید، باید مراحل زیر را انجام دهید:
ابتدا متدی را برای دریافت محتوای تولید شده بنویسید. برای مثال برای دریافت محتوای تولیده شده‌ی فایل hubs.js می‌توانید از متد زیر استفاده کنید:
public static string GetSignalRContent()
{
    var resolver = new DefaultHubManager(new DefaultDependencyResolver());
    var proxy = new DefaultJavaScriptProxyGenerator(resolver, new NullJavaScriptMinifier());
    return proxy.GenerateProxy("/signalr");
}
سپس باید سیستم مسریابی پیش فرض سیستم Bundling را با سیستم مسیریابی سفارشی خود جایگزین کنید. کاری که باید انجام دهید اینست که در سیستم مسیریابی سفارشی خود چک کنید اگر مسیر درخواستی به مسیر مورد نظر شما اشاره دارد، مقدار true را برگشت دهید. در واقع در سیستم مسیریابی پیش فرض اگر فایلی بطور فیزیکی وجود نداشته باشد، مقدار برگشتی false خواهد بود.
همچنین در این سیستم مسیریابی سفارشی شما باید محتوای تولید شده را هم در اختیار داشته باشید. برای نمونه به کد زیر توجه کنید که ما از کلاس VirtualPathProvider، یک کلاس مشتق کرده و سیستم مسیریابی دلخواه خود را ایجاد می‌کنیم:

public class CustomVirtualPathProvider : VirtualPathProvider
{
    public CustomActionVirtualPathProvider(VirtualPathProvider virtualPathProvider)
    {
        // Wrap an existing virtual path provider
        VirtualPathProvider = virtualPathProvider;
    }

    protected VirtualPathProvider VirtualPathProvider { get; set; }

    public override string CombineVirtualPaths(string basePath, string relativePath)
    {
        return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath);
    }

    public override bool DirectoryExists(string virtualDir)
    {
        return VirtualPathProvider.DirectoryExists(virtualDir);
    }

    public override bool FileExists(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return true;
        }

        return VirtualPathProvider.FileExists(virtualPath);
    }

    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        // BaseClass can't create a CacheDependency for your content, remove it
        // You could also add your own CacheDependency and aggregate it with the base dependency
        List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
        virtualPathDependenciesCopy.Remove("~/signalr/hubs");

        return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart);
    }

    public override string GetCacheKey(string virtualPath)
    {
        return VirtualPathProvider.GetCacheKey(virtualPath);
    }

    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        return VirtualPathProvider.GetDirectory(virtualDir);
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return new CustomVirtualFile(virtualPath,
                new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent())));
        }

        return VirtualPathProvider.GetFile(virtualPath);
    }

    public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
    {
        return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies);
    }

    public override object InitializeLifetimeService()
    {
        return VirtualPathProvider.InitializeLifetimeService();
    }
}

public class CustomVirtualFile : VirtualFile
{
    public CustomVirtualFile (string virtualPath, Stream stream)
        : base(virtualPath)
    {
        Stream = stream;
    }

    public Stream Stream { get; private set; }

    public override Stream Open()
    {
         return Stream;
    }
}
ابتدا در بازنویسی متد FileExists باید چک کنیم اگر مسیر درخواستی به مسیر مورد نظر ما اشاره داشت، منطق خود را پیاده کنیم:
    public override bool FileExists(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return true;
        }

        return VirtualPathProvider.FileExists(virtualPath);
    }
  در این متد اگر مسیر درخواستی مطابق مسیر مورد نظر ما بود باید true برگشت دهیم، در غیر اینصورت کار را به کلاس پایه می‌سپاریم.
همچنین باید توجه داشت که کلاس پایه، قادر به تولید CacheDependency  برای محتوای تولیدی شده ما نیست. بنابراین باید متد GetCacheDependency کلاس پایه‌ی ما بازنویسی و منطق مورد نظر را برای آن پیاده سازی کنیم. در این متد ابتدا مسیر مورد نظر خود را از لیست مسیرهایی که باید از CacheDependency استفاده کنند، حذف و سپس سناریوی خود را پیاده سازی می‌کنیم. در این مثال من از پیاده سازی آن خودداری کرده و فقط مسیر را از لیست مسیرها حذف کرده‌ام:
    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
        virtualPathDependenciesCopy.Remove("~/signalr/hubs");

        return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart);
    }
سپس نوبت دریافت محتوای تولید شده‌ی پویا است که باید با بازنویسی متد GetFile منطق خود را اعمال کنیم:
    public override VirtualFile GetFile(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return new CustomVirtualFile(virtualPath,
                new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent())));
        }

        return VirtualPathProvider.GetFile(virtualPath);
    }
در این متد ابتدا چک می‌کنیم اگر مسیر درخواست شده به مسیر مورد نظر ما اشاره داشت، با استفاده از متد GetSignalRContent محتوای تولید شده‌ی پویا را دریافت و از طریق کلاس CustomVirtualFile که از کلاس VirtualFile مشتق کرده ایم، آن‌را باز می‌گردانیم. کار این کلاس هم فراهم کردن بستری برای اشیایی است که یک فایل فیزیکی را در قالب سیستم فایل مجازی بکار می‌گیرند. تنها نکته‌ی قابل توجه در این کلاس، بازنویسی متد Open کلاس پایه، برای بازگرداندن یک Stream فقط خواندنی از محتوای منبع مورد نظر ماست. از آنجا که منبع ما در اینجا به طور پویا تولید می‌شود، همانطور که دیدید ما در متد GetFile از یک MemoryStream استفاده کردیم؛ در صورتیکه اگر با یک فایل فیزیکی سر و کار داشتیم، ممکن بود از یک FileStream استفاده کنیم.
در نهایت باید کلاس سفارشی شده را با سیستم مسیریابی پیش فرض سیستم Bundling جایگزین کنیم:
public static void RegisterBundles(BundleCollection bundles)
{
    BundleTable.VirtualPathProvider =
        new CustomVirtualPathProvider(BundleTable.VirtualPathProvider);

    Bundle include = new Bundle("~/bundle")
        .Include("~/Content/static.js")
        .Include("~/signalr/hubs");

    bundles.Add(include);
}
نظرات اشتراک‌ها
فرق بین TFS ،SVN و GIT در چیست؟
من از SVN استفاده می‌کنم  و قصد تعویضش رو هم ندارم. SVN مانند Git توزیع شده نیست و ... برای کار من هم اهمیتی ندارد. سازگاری بسیار مناسبی با ویندوز دارد. برنامه Visual SVN Server و همچنین افزونه VisualSVN، کار کردن با SVN را تحت ویندوز و با VS.NET بسیار لذت بخش می‌کنند. تمام امکانات یک سورس کنترل عالی را هم دارد و من کم و کسری در آن نمی‌بینم.

نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
مراجعه کنید به قسمت پروژه‌های سایت. پروژه‌های سورس باز «طراحی فریمورک برای کار با Asp.net MVC و EF به صورت NTier»، «فروشگاه اینترنتی شهر طلایی من » و « سیستم مدیریت محتوای IRIS» از همین روش الگوی واحد کار استفاده می‌کنند.     
نظرات مطالب
بهبود SEO در ASP.NET MVC
- این یک کار سورس باز هست. مطابق نیاز خودتون تغییرش بدید و استفاده کنید. برای استفاده‌ای که من دارم تنظیمات آن کافی است.
- در کار من قسمت‌هایی که کوئری استرینگ دارند عمومی نیستند و نیاز به اعتبارسنجی دارند. به همین جهت تبدیل آن‌ها برای بحث SEO اهمیتی نخواهند داشت. خصوصا قسمت کوئری استرینگ آن‌ها نباید تغییری کند چون در رمزگشایی اطلاعات از آن استفاده می‌شود.
مطالب
چرا باید از Git Hooks استفاده کنیم؟ معرفی Husky.Net
قبل از شروع این مقاله بهتر است ابتدا یک خاطره‌ی کوچک را تعریف کنم. مدتی پیش بود که برای سایت داکیومنتیشن یکی از پروژه‌های Open-Source سعی داشتم از vuepress که یک static site generator هست استفاده کنم. متاسفانه نسخه‌ی بتایی که استفاده میکردم یک فیچر مورد نیازم را نداشت و مجبور شدم خودم به‌آن این فیچر را اضافه کنم. سوروس را گرفتم، فیچر اضافه شد و ماجرا از اینجا شروع می‌شود ...

  • commit lint : اول اجازه نداد که کامیت را انجام دهم! با این توضیح که متن کامیت شما باید فرمت کامیت‌های این پروژه را رعایت کند. اگر به تصویر زیر دقت کنید، کامیت‌ها در این پروژه بسیار منظم هستند و هر کسی با هر متنی که دلش خواست کامیتی انجام نمی‌دهد. با یک بررسی کوچک متوجه شدم چطور باید فرمت این پروژه را رعایت کنم و مشکل رفع شد.



  • eslint : مرحله بعدی فایل‌هایی که تغییر داده بودم و یا ایجاد کرده بودم، با ابزار eslint به صورت خودکار بررسی شد. (eslint مشابه analyzer‌های دات نت است که برای بررسی مشکلات موجود در سورس کد استفاده می‌شود). در تصویر زیر یک مثال از خطای ایجاد شده‌ی توسط eslint را مشاهده می‌کنید. در این مرحله من با خطایی مواجه نشدم؛ ولی متوجه شدم کدی که نوشتم، مشکل خاصی ندارد. 



  • prettier  : مرحله سوم کد نوشته شده‌ی من توسط دستورات فرمتی که این پروژه رعایت می‌کند مانند style کد نویسی، استفاده از space یا tab، فاصله فرو رفتگی (indent) خطوط و غیره ... به صورت خودکار فرمت شد و کامیت من انجام شد. 

داشتن ابزاری که چنین کارهایی را به صورت خودکار انجام دهد، حتی اگر به تنهایی بر روی یک پروژه کار کنیم، بسیار مفید است و کیفیت کار و کد تولید شده را بالا می‌برد. ناگفته نماند که این موارد، یکی از اصلی‌ترین دغدغه‌های نگهداری پروژه است؛ بخصوص وقتی بیش از یک برنامه نویس داشته باشیم. به طور مثال قبل از اینکه من این امکانات را به پروژه‌های خودم اضافه کنم، با تیمی که کار میکردم، همیشه سعی داشتیم که از تنظیمات و حتی IDE مشترکی استفاده کنیم تا مثلا formatter کد ما مثل هم باشد. اگر این موارد قبل از هر کامیت انجام شوند، برنامه نویس‌ها میتوانند حتی در notepad کد نویسی انجام دهند و اطمینان داشته باشند، کد نوشته شده، هم اعتبار سنجی می‌شود و هم به صورت خودکار فرمت. 

تا اینجا با یکسری مزایای پایه‌ی داشتن چنین سیستمی آشنا شدیم ولی چطور این اتفاق رخ می‌دهد!؟ پاسخ: با استفاده از Git Hooks . البته ما صرفا محدود به انجام همین کارها نیستیم. هر کاری که در ترمینال سیستم قابل انجام باشد، از طریق Git hooks هم قابل انجام است. مثلا یکی دیگر از استفاده‌های رایج از git hooks میتواند اجرای Unit-Test‌ها قبل از push باشد.
در پروژه‌ی vuepress، تعداد زیادی ابزار به صورت هماهنگ نصب شده و کار میکنند که در نهایت با استفاده از Hook‌های Git، اجرا میشوند (مثلا از هوک pre-commit برای اجرای یکسری فرمان قبل از هر کامیت). با سه مورد از این ابزارها، در ابتدای این مقاله آشنا شدیم. ولی کلید اصلی تمام این‌ها، دو ابزار  husky  و  lint-staged هستند. husky ساخت و استفاده از git hook‌ها را آسان میکند و lint-staged وظیفه دارد تا فایل‌های stage شده‌ی در گیت را لیست کند و به سایر ابزارها برای اجرا ارسال نماید. 

شاید بپرسید که تمام اینها از Nodejs و npm استفاده میکنند، چطور میتوانیم از این امکانات در دات نت استفاده کنیم!   

معرفی Husky.Net 
این کتابخانه ترکیبی از ابزارهای husky و lint-staged و چند ابزار دیگر برای برنامه نویسان دات نت است. با استفاده از این ابزار به راحتی میتوانید از هوک‌های Git برای اجرای دستورات خود یا سایر ابزارها استفاده کنید. husky.net یک task-runner داخلی دارد که تعریف و ایجاد تسک‌های قابل اجرا توسط هوک‌ها را آسان میکند.

نصب husky.net
# local installation (recommended)
cd <Your project root directory>
dotnet new tool-manifest
dotnet tool install Husky

# global installation
dotnet tool install --global Husky

نکته نهایی
من در این مقاله قصد داشتم فقط دلیل نیاز به چنین ابزاری را شرح دهم؛ توضیح اینکه husky.net چه امکاناتی دارد و چطور میتوان از آن استفاده کرد، خارج از حوصله‌ی این مقاله است. اگر شد در آینده مقاله‌ای برای آموزش این ابزار تهیه میکنم؛ فعلا برای کسب اطلاعات بیشتر میتوانید به صفحه‌ی گیت هاب این ابزار مراجعه کنید و دلیل دیگر اینکه فکر میکنم الان زمان مناسبی برای آموزش نیست؛ چون این پروژه بسیار جوان است و به تازگی نوشته شده و احتمال اینکه فیچر‌های آن به زودی تغییر کند هم وجود دارد. همینطور از علاقمندان به مشارکت در پروژه‌های open-source دعوت می‌کنم که به بهبود این ابزار و یا تهیه‌ی ابزارهایی که در حال حاضر مشابهی در دات نت ندارند، کمک کنند. به طور مثال برای فرمت کد و آنالایزر، dotnet-format  مایکروسافت وجود دارد که بسیار کاربردیست؛ ولی ما ابزاری مثل commit lint هنوز در دات نت نداریم. فعلا در صورت نیاز باید برای استفاده از آن همچنان node و npm را نصب کنیم.
اشتراک‌ها
دلایل ارتقاء به SQL Server 2017

Following is a chart that shows licensing cost comparison between standard and Enterprise Edition of SQL Server 2012, 2016 and 2017.

Version Edition License Cost 2 Quad core
Processors
4 Quad core
Processors
    Per Core 8 Cores 16 Cores
SQL Server 2012 Standard $1,793 $14,344 $28,688
SQL Server 2012 Enterprise $6,874 $54,992 $109,984
SQL Server 2016\ 2017 Standard $1,858 $14,864 $29,728
SQL Server 2016 \2017 Enterprise $7,128 $57,024 $114,048
دلایل ارتقاء به SQL Server 2017
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
بله اون مطلب رو مطالعه کردم
محتویات فایل package.json به این صورت هست
{
  "name": "dntcommon.web.core.testwebapp",
  "version": "1.0.0",
  "description": "",
  "scripts": {},
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^3.3.7",
    "bootstrap-rtl": "^3.3.4",
    "components-font-awesome": "5.0.6",
    "jquery": "^3.3.1",
    "jquery-ajax-unobtrusive": "^3.2.4",
    "jquery-validation": "^1.17.0",
    "jquery-validation-unobtrusive": "^3.2.8",
    "samim-font": "1.0.2"
  }
}

بعد از اجرای npm install  پاسخ زیر رو می‌دهد
C:\Projects\ZagrosCore\src\Zagros>npm install
npm WARN dntcommon.web.core.testwebapp@1.0.0 No description
npm WARN dntcommon.web.core.testwebapp@1.0.0 No repository field.

up to date in 0.181s
احتمالا ساختار فایل package.json اشتباه نیست؟ 
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
سلام ،‌ چند تا مورد را خواستم متذکر بشم. درست است که ورژن VisualStudio Express مجانی است ، ولی همین که امکان اجرای Addin نداره باعث محدودیت خیلی زیادی میشه. اکثر کسانی که از Test Runner ها استفاده میکنند از نوعی Addin برای اجرای اونها استفاده میکنند بطوری که بدون ساپورت Addin استفاده از این نسخه برای این افراد غیر ممکنه میشه.

نکته دومی که بهش اشاره نکردید ، این است که در خود اروپا و امریکا  شرکت های نرم افزاری Microsoft Partner پولی بابت License برای VisualStudio نمیدند. شرکت هایی هم که پول میدهند بابت MSDN Subscription میدهند که مثلا با ده هزار دلار تا یک سال تقریبا به همه محصولات دسترسی دارند و جدا جدا پولی پرداخت نمیکنند. شرکت هایی هم که Microsoft Partner باشند معمولا یک هزینه خیلی پایین (در حد پنج هزار دلار) در سال میدهند بابت حدود صد نفر برنامه نویس که این شامل هزینه سرور ها ،‌ دیتابیس ، ویندوز و ... میشود. البته در حال حاظر در ایران به علت تحریم عملا این روش قابل استفاده نیست.
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
سلام ،‌ چند تا مورد را خواستم متذکر بشم. درست است که ورژن VisualStudio Express مجانی است ، ولی همین که امکان اجرای Addin نداره باعث محدودیت خیلی زیادی میشه. اکثر کسانی که از Test Runner ها استفاده میکنند از نوعی Addin برای اجرای اونها استفاده میکنند بطوری که بدون ساپورت Addin استفاده از این نسخه برای این افراد غیر ممکنه میشه.

نکته دومی که بهش اشاره نکردید ، این است که در خود اروپا و امریکا  شرکت های نرم افزاری Microsoft Partner پولی بابت License برای VisualStudio نمیدند. شرکت هایی هم که پول میدهند بابت MSDN Subscription میدهند که مثلا با ده هزار دلار تا یک سال تقریبا به همه محصولات دسترسی دارند و جدا جدا پولی پرداخت نمیکنند. شرکت هایی هم که Microsoft Partner باشند معمولا یک هزینه خیلی پایین (در حد پنج هزار دلار) در سال میدهند بابت حدود صد نفر برنامه نویس که این شامل هزینه سرور ها ،‌ دیتابیس ، ویندوز و ... میشود. البته در حال حاظر در ایران به علت تحریم عملا این روش قابل استفاده نیست.