مطالب
ASP.NET MVC #23

اجرای برنامه‌های ASP.NET MVC توسط نگارش‌های متفاوت IIS

تا اینجا برای اجرای برنامه‌های ASP.NET MVC از وب سرور توکار VS.NET استفاده شد که صرفا جهت آزمایش برنامه‌ها طراحی شده است. تا این تاریخ سه رده از وب سرورهای مایکروسافت ارائه شده‌اند که برای نصب ASP.NET MVC می‌توانند مورد استفاده قرار گیرند و هر کدام هم نکته‌های خاص خودشان را دارند که در ادامه به بررسی آن‌ها خواهیم پرداخت.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP

پس از ایجاد یک دایرکتوری مجازی بر روی پوشه یک برنامه ASP.NET MVC و سعی در اجرای برنامه، بلافاصله پیغام خطای HTTP 403 forbidden مشاهده می‌شود.
اولین کاری که برای رفع این مساله باید صورت گیرد، کلیک راست بر روی نام دایرکتوری مجازی در کنسول IIS، انتخاب گزینه خواص و سپس مراجعه به برگه «ASP.NET» آن است. در اینجا شماره نگارش دات نت فریم ورک مورد استفاده را به 4 تغییر دهید (برای نمونه ASP.NET MVC 3.0 مبتنی بر دات نت فریم ورک 4 است).
بعد از این تغییر، بازهم موفق به اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x نخواهیم شد؛ چون در آن زمان مفاهیم مسیریابی و Routing که اصل و پایه ASP.NET MVC هستند وجود خارجی نداشتند. این نگارش از IIS به صورت پیش فرض تنها قادر به پردازش درخواست‌های رسیده‌ای که به یک فایل فیزیکی بر روی سرور اشاره می‌کند، می‌باشد (یعنی مشکلی با اجرای برنامه‌های ASP.NET Web forms ندارد).
برای رفع این مشکل، مجددا بر روی نام دایرکتوری مجازی برنامه در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه ظاهر شده، در برگه «Virtual directory» آن، بر روی دکمه «Configuration» کلیک نمائید. در صفحه باز شده مجددا بر روی دکمه «Add» کلیک کنید.
در صفحه باز شده، مسیر Executable را C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll وارد کرده و Extension را به .* (دات هرچی) تنظیم کنید. همین مقدار تنظیم، برای اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP کفایت می‌کند.

کاری که در اینجا انجام شده است، نگاشت تمام درخواست‌های رسیده صرفنظر از پسوند فایل‌ها، به موتور ASP.NET می‌باشد. به صورت پیش فرض در IIS 5.x درخواست‌ها تنها بر اساس پسوند فایل‌ها پردازش می‌شوند. مثلا اگر فایل درخواستی aspx است، درخواست رسیده به aspnet_isapi.dll یاد شده هدایت خواهد شد. اگر پسوند فایل php است به isapi مخصوص آن (در صورت نصب) هدایت می‌گردد و به همین ترتیب برای سایر سیستم‌های دیگر. زمانیکه Extension به «دات هرچی» و Executable به aspnet_isapi.dll دات نت 4 تنظیم می‌شود، دایرکتوری مجازی تنظیم شده تنها جهت سرویس دهی به یک برنامه ASP.NET عمل خواهد کرد و تمام درخواست‌های رسیده به آن، به موتور اجرایی ASP.NET هدایت می‌شوند.

بدیهی است تنظیمات فوق تنها به یک دایرکتوری مجازی اعمال شدند. اگر نیاز باشد تا بر روی تمام سایت‌ها تاثیر گذار شود، اینبار در کنسول IIS 5.x بر روی «Default web site» کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه باز شده به برگه «Home directory» مراجعه کرده و مراحل ذکر شده را تکرار کنید.

مشکل! این روش بهینه نیست.
روش فوق خوبه، کار می‌کنه، اما بهینه نیست؛ از این جهت که «نگاشت تمام درخواست‌ها به موتور ASP.NET» یعنی پروسه پردازش درخواست یک فایل تصویری، js یا css هم باید از فیلتر موتور ASP.NET عبور کند که ضروری نیست.
برای رفع این مشکل، توصیه شده است که سیستم مسیریابی ASP.NET MVC را در IIS 5.x «پسوند دار» کنید. به این نحو که با مراجعه به فایل Global.asax.cs، تعاریف مسیریابی را به نحو زیر ویرایش کنید:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
new Route("{controller}.aspx/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
})
});



اینبار برای مثال مسیر http://localhost/MyMvcApp/home.aspx/index به علت داشتن پسوند aspx وارد موتور پردازشی ASP.NET خواهد شد. البته در این حالت URL های تمیز ASP.NET MVC را از دست خواهیم داد و مدام باید دقت داشت که مسیرهای کنترلرها حتما باید به aspx ختم شوند. ضمنا با این تنظیم، دیگر نیازی به تغییر تعاریف نگاشت‌ها در کنسول مدیریتی IIS، نخواهد بود.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 6.x ویندوز سرور 2003

تمام نکات عنوان شده جهت IIS 5.x در IIS 6.x نیز صادق هستند. به علاوه برای اجرای برنامه‌های ASP.NET بر روی IIS 6.x باید به دو نکته مهم دیگر نیز دقت داشت:
الف) ASP.NET 4 به صورت پیش فرض در IIS 6.x غیرفعال است که باید با مراجعه به قسمت Web Services Extensions در کنسول مدیریتی IIS، آن‌را از حالت prohibited خارج کرد.
ب) در هر Application pool تنها از یک نگارش دات نت فریم ورک می‌توان استفاده کرد. برای مثال اگر هم اکنون AppPool1 مشغول سرویس دهی به یک سایت ASP.NET 3.5 است، از آن نمی‌توانید جهت اجرای برنامه‌های ASP.NET MVC 3 به بعد استفاده کنید. زیرا برای مثال ASP.NET MVC 3 مبتنی بر دات نت فریم ورک 4 است. به همین جهت حتما نیاز است تا یک Application pool مجزا را برای برنامه‌های دات نت 4 در IIS 6 اضافه نمائید و سپس در تنظیمات سایت، از این Application pool جدید استفاده نمائید.
البته روش صحیح و اصولی کار با IIS از نگارش 6 به بعد هم مطابق شرحی است که عنوان شد. برای دستیابی به بهترین کارآیی و امنیت بیشتر، بهتر است به ازای هر سایت، از یک Application pool مجزا استفاده نمائید.

اطلاعات تکمیلی:
نکات نصب برنامه‌های ASP.NET 4.0 بر روی IIS 6
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS



اجرای برنامه‌های ASP.NET MVC بر روی IIS 7.x ویندوز 7 و ویندوز سرور 2008

اگر برنامه ASP.NET MVC در IIS 7.x در حالت یکپارچه (integrated mode) اجرا شود، بدون نیاز به هیچگونه تغییری در تنظیمات سرور یا برنامه، بدون مشکل قابل اجرا خواهد بود. بدیهی است در اینجا نیز بهتر است به ازای هر برنامه، یک Application pool مجزا را ایجاد کرد.
اما در حالت classic (که برای برنامه‌های جدید توصیه نمی‌شود) نیاز است همان مراحل IIS 5,x تکرار شود. البته اینبار مسیر زیر را باید طی کرد تا به صفحه افزودن نگاشت‌ها رسید:
Right-click on a web site -> Properties -> Home Directory tab -> click on the Configuration button -> Mappings tab



نکته‌ای مهم در تمام نگارش‌های IIS

ترتیب نصب دات نت فریم ورک 4 و IIS مهم است. اگر ابتدا IIS نصب شود و سپس دات نت فریم ورک 4، به صورت خودکار، کار نگاشت اطلاعات ASP.NET به IIS صورت خواهد گرفت.
اگر ابتدا دات نت فریم ورک 4 نصب شود و سپس IIS، برای مثال دیگر از برگه ASP.NET در IIS 6.x خبری نخواهد بود. برای رفع این مشکل دستور زیر را در خط فرمان اجرا کنید:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe /i

به این ترتیب، اطلاعات مرتبط با موتور ASP.NET مجددا به تنظیمات IIS اضافه خواهند شد.


مطالب
آشنایی با 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 را می‌پذیرند و برای سایر کنترل‌ها باید با کمی تغییر در کد و نوع برگشتی که تحویل خروجی متد مبدل می‌شود، دهید.

دانلود فایل‌های این قسمت
نظرات مطالب
روش یافتن لیست تمام کنترلرها و اکشن‌ متدهای یک برنامه‌ی ASP.NET Core
یک نکته‌ی تکمیلی: لیست کردن تمام مسیریابی‌های یک برنامه‌ی ASP.NET Core

با اضافه شدن Minimal API, Controllers, Razor Pages, gRPC, Health checks به ASP.NET Core، اینبار جهت یافتن تمام مسیریابی‌های موجود در برنامه می‌توان از سرویس توکار EndpointDataSource استفاده کرد. فقط کافی است <IEnumerable<EndpointDataSource را از سیستم تزریق وابستگی‌های برنامه درخواست کنید تا لیست تمام endpoints موجود را ارائه دهد:
app.MapGet("/debug/routes", (IEnumerable<EndpointDataSource> endpointSources) =>
        string.Join("\n", endpointSources.SelectMany(source => source.Endpoints)));
مطالب
لینک‌های هفته‌ی اول بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

Visual Studio


ASP. Net


طراحی و توسعه وب


اس‌کیوال سرور


سی شارپ


عمومی دات نت


ویندوز


مسایل اجتماعی و انسانی برنامه نویسی


متفرقه

مطالب
جلوگیری از Brute Force Attack
یکی از حملات رایج که توسعه دهندگان با آن روبرو هستند، Brute-Force Attack میباشد. در این حملات برای کشف رمزعبور کاربران، هر ترکیب احتمالی از حروف، اعداد و نمادها را استفاده میکنند تا یک ترکیب صحیح را بدست آورند. اگر وب سایت شما نیاز به احراز هویت دارد، شما یک هدف مناسب برای حملات Brute-Force هستید! یک هکر میتواند رمزعبور را از این طریق بدست آورد؛ اما یافتن آن شاید سالها طول بکشد که بستگی به طول رمز و پیچیدگی آن، ممکن است چندین سال طول بکشد. اما یک حمله Brute-Force میتواند سریع‌تر انجام شود، میتواند از فرهنگ لغت استفاده کند و یا کلمات فرهنگ لغت را کمی تغییر دهد، زیرا بیشتر افراد بجای یک کلمه‌ی عبور تصادفی و پیچیده، از آن کلمات استفاده میکنند. به این حملات dictionary attacks یا hybrid brute-force attacks نیز گفته میشود. این حملات حساب‌های کاربران را در معرض خطر قرار میدهد و باعث افزایش ترافیک غیرضروری سایت شما میشود.

هکرها از ابزارهای گسترده‌ای که لیست کلمات و قواعد هوشمندانه‌ای را دارند استفاده میکنند تا به طور هوشمندانه و به طور خودکار، کلمه‌های عبور کاربران را حدس بزنند. اگرچه شناسایی چنین حملاتی آسان است، اما جلوگیری از آن کار آسانی نیست. به طور مثال برخی از ابزارهای HTTP brute-force میتوانند درخواست‌ها را از طریق پروکسی‌های باز سرور انجام دهند و از آنجا که هر درخواست از یک IP متفاوت میرسد، نمیتوانید با مسدود کردن IP، حملات را مسدود کنید. حتی برخی از ابزارها برای هر بار درخواست، یک نام کاربری و یک کلمه عبور را امتحان میکنند و شما نمیتوانید هر حساب کاربری را برای یک بار عملیات ناموفق مسدود کنید.


مسدود کردن حساب‌ها

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

مسدود کردن حساب‌ها  گاهی اوقات تاثیرگذار است؛ اما در محیط‌های کنترل شده. با این حال در بیشتر موارد مسدود کردن حساب‌ها برای متوقف کردن حملات brute-force کافی نیست. به عنوان مثال یک سایت حراجی را در نظر بگیرید که چندین داوطلب برای یک کالا در حال مسابقه هستند. اگر وبسایت حراجی حساب‌ها را مسدود کند، یک پیشنهاد دهنده میتواند در لحظات آخر نسبت به مسدود کردن حساب‌های دیگران اقدام کند و مانع از ارائه هرگونه پیشنهادی شود و خود برنده شود. یک مهاجم میتواند از همین تکنیک برای مسدود کردن معاملات حساس مالی و ... اقدام کند.


اقدامات متقابل

همانطور که توضیخ داده شد، مسدود کردن حساب‌ها یک راه کامل برای مقابله با این حملات نیست. اما ترفندهای دیگری نیز برای مقابله با این حملات وجود دارند. از آنجا که موفقیت این حملات به زمان بستگی دارد، یک راه حل آسان، تزریق مکث‌های تصادفی هنگام چک کردن رمز عبور است. افزودن مکث حتی چند ثانیه‌ای میتواند یک حمله را بسیار کند نماید؛ اما بیشتر کاربران را هنگام ورود به سایت، ناراحت نمیکند. توجه داشته باشید اگرچه اضافه کردن تاخیر میتواند یک حمله single-thread را کند، کند اما اگر مهاجم چندین درخواست را همزمان ارسال کند، کمتر موثر است.

راه حل دیگر مسدود کردن یک IP با چند عملیات ناموفق است. اما مشکل این راه حل آن است که شما سهوا گروه‌هایی از کاربران را بلاک میکنید؛ مانند بلاک کردن یک پراکسی سرور که توسط یک ISP استفاده میشود. مشکل دیگر این راه حل استفاده‌ی بسیاری از ابزارها از لیستی از پراکسی‌ها است و با هر IP، دو یا سه درخواست را ارسال میکنند و سپس به دنبال بعدی می‌روند. یک مهاجم میتواند به این طریق به راحتی فرآیند مسدود کردن IP را دور بزند. بیشتر سایت‌ها حساب‌ها را بعد از یک عملیات ناموفق، مسدود نخواهند کرد؛ به همین دلیل یک مهاجم میتواند از هر پراکسی، دو یا سه درخواست را امتحان کند. یک هکر با داشتن لیستی از 1000 پراکسی میتواند 2000 یا 3000  کلمه عبور را امتحان کند؛ بدون مسدود شدن حساب‌ها.

 یک راه حل ساده در عین حال موثر آن است که رفتار وب سایت خود را برای پاسخ به عملیات ناموفق ورود به سایت، درست طراحی نکنید. برای مثال اکثر وب سایت‌ها کد "HTTP 401 error" را برای کلمه عبور اشتباه میفرستند. اگرچه بعضی وب سایت‌ها کد "HTTP 200 SUCCESS" را برمیگردانند، اما کاربر را به صفحه‌ای هدایت میکند و توضیح میدهد که رمز عبور را اشتباه وارد کرده است، این کار برخی سیستم‌ها را فریب می‌دهد اما دور زدن آن نیز راحت است. یک راه حل بهتر آن است که هر بار از پیام‌های خطای مختلفی استفاده کنید و یا گاهی به کاربر اجازه رفتن به یک صفحه را بدهید و دوباره کاربر را وادار به وارد کردن رمز عبور نمایید.

بعضی از ابزارها این امکان را دارند تا مهاجم یک رشته را وارد کند تا به دنبال آن باشند که نشان میدهد عملیات ورود ناموفق بوده است. به عنوان مثال اگر صفحه‌ای شامل عبارت "Bad username or password" باشد، به معنای آن است که عملیات ناموفق بوده است و نام کاربری و یا کلمه‌ی عبور بعدی را امتحان میکند. یک راه ساده برای مقابله با این راه این است که از عبارت‌هایی استفاده کنیم که در هنگام ورود موفق استفاده میشوند.

بعد از دو یا چند تلاش ناموفق، کاربر را وادار به پاسخ دادن به یک سؤال مخفی کنید. این کار نه تنها باعث اختلال در حملات خودکار میشود بلکه از دسترسی فرد مهاجم جلوگیری میکند؛ حتی اگر نام کاربری و رمز عبور را درست وارد کرده باشد.

سایر تکنیک هایی که میتوانید در نظر بگیرید عبارتند از:
  • برای کاربران مهم که میخواهند از حساب خود در مقابل این نوع حملات جلوگیری کنند، به آنها این امکان را  بدهید که بتوانند فقط از IP خاصی وارد سیستم شوند.
  • برای جلوگیری از حملات خودکار، از captcha استفاده کنید.
  • بجای قفل کردن حساب کاربری، آنرا در حالت قفل با دسترسی محدود قرار دهید.

هکرها اغلب میتوانند بسیاری از تکنیک‌ها را به تنهایی دور بزنند. اما با ترکیب چندین حالت میتوانید این حملات را به میزان قابل توجهی کاهش دهید. اگرچه جلوگیری کامل از این حملات سخت است، اما تشخیص آن آسان است؛ زیرا با هربار ورود ناموفق، یک رکورد با کد HTTP 401 در لاگ‌های سرور ثبت میشود. این مهم است که لاگ‌های سرور خود را برای این نوع حملات نظارت کنید در شرایط خاص. کد 200 به آن معناست که مهاجم یک رمز عبور معتبر را پیدا کرده است.

شرایطی که میتوانند حملات brute-force یا سوء استفاده از یک حساب را نشان دهند:
  • تعداد زیادی ورود ناموفق از یک IP
  • تلاش برای ورود به سایت با چند نام کاربری از طرف یک IP
  • تلاش برای ورود به یک حساب کاربری از طرف چندین IP
  • تلاش برای ورود، با وارد کردن نام کاربری و کلمه‌ی عبور، به ترتیب حروف الفبا
  • تلاش برای ورود، با نام کاربری یا کلمات عبوری مانند ownsyou,washere,zealots ,hacksyou یا مشابه آنها که هکرها معمولا از آنها استفاده میکنند

متوقف کردن این حملات دشوار است؛ اما با طراحی دقیق و اقدامات متقابل متعدد، می‌توانید میزان قرار گرفتن در معرض این حملات را محدود کنید. درنهایت، بهترین راه جلوگیری این است که کاربران از قوانین اصلی رمزهای عبور قوی استفاده کنند؛ برای مثال از رمزهای عبور طولانی غیرقابل پیش بینی، اجتناب از کلمات فرهنگ لغت، جلوگیری از استفاده مجدد از گذرواژه‌ها و تغییر گذرواژه‌ها به طور منظم.   
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 2 - بررسی ساختار جدید Solution
اگر یک پروژه‌ی خالی ASP.NET Core Web Application را شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
 .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
تغییرات ساختاری ASP.NET Core 1.0، با نگارش‌های قبلی ASP.NET، بسیار قابل ملاحظه هستند:


در اینجا نقش Solution همانند نگارش‌های قبلی ویژوال استودیو است: ظرفی است برای ساماندهی موارد مورد نیاز جهت تشکیل یک برنامه‌ی وب و شامل مواردی است مانند پروژه‌ها، تنظیمات آن‌ها و غیره. بنابراین هنوز در اینجا فایل sln. تشکیل می‌شود.


نقش فایل global.json

زمانیکه یک پروژه‌ی جدید ASP.NET Core 1.0 را آغاز می‌کنیم، ساختار پوشه‌های آن به صورت زیر هستند:


در اینجا هنوز فایل sln. قابل مشاهده است. همچنین در اینجا فایل جدیدی به نام global.json نیز وجود دارد، با این محتوا:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003121"
  }
}
شماره نگارش ذکر شده‌ی در اینجا را در قسمت قبل بررسی کردیم.
خاصیت projects در اینجا به صورت یک آرایه تعریف شده‌است و بیانگر محل واقع شدن پوشه‌های اصلی پروژه‌ی جاری هستند. پوشه‌ی src یا source را در تصویر فوق مشاهده می‌کنید و محلی است که سورس‌های برنامه در آن قرار می‌گیرند. یک پوشه‌ی test نیز در اینجا ذکر شده‌است و اگر در حین ایجاد پروژه، گزینه‌ی ایجاد unit tests را هم انتخاب کرده باشید، این پوشه‌ی مخصوص نیز ایجاد خواهد شد.
نکته‌ی مهم اینجا است، هرکدی که درون پوشه‌های ذکر شده‌ی در اینجا قرار نگیرد، قابلیت build را نخواهد داشت. به عبارتی این نسخه‌ی از ASP.NET پوشه‌ها را قسمتی از پروژه به حساب می‌آورد. در نگارش‌های قبلی ASP.NET، مداخل تعریف فایل‌های منتسب به هر پروژه، درون فایلی با پسوند csproj. قرار می‌گرفتند. معادل این فایل در اینجا اینبار پسوند xproj را دارد و اگر آن‌را با یک ادیتور متنی باز کنید، فاقد تعاریف مداخل فایل‌های پروژه است.
در این نگارش جدید اگر فایلی را به پوشه‌ی src اضافه کنید یا حذف کنید، بلافاصله در solution explorer ظاهر و یا حذف خواهد شد.
یک آزمایش: به صورت معمول از طریق windows explorer به پوشه‌ی src برنامه وارد شده و فایل پیش فرض Project_Readme.html را حذف کنید. سپس به solution explorer ویژوال استودیو دقت کنید. مشاهده خواهید کرد که این فایل، بلافاصله از آن حذف می‌شود. در ادامه به recycle bin ویندوز مراجعه کرده و این فایل حذف شده را restore کنید تا مجددا به پوشه‌ی src برنامه اضافه شود. اینبار نیز افزوده شدن خودکار و بلافاصله‌ی این فایل را می‌توان در solution explorer مشاهده کرد.
بنابراین ساختار مدیریت فایل‌های این نگارش از ASP.NET در ویژوال استودیو، بسیار شبیه به ساختار مدیریت فایل‌های VSCode شده‌است که آن نیز بر اساس پوشه‌ها کار می‌کند و یک پوشه و تمام محتوای آن‌را به صورت پیش فرض به عنوان یک پروژه می‌شناسد. به همین جهت دیگر فایل csproj ایی در اینجا وجود ندارد و file system همان project system است.

یک نکته: در اینجا مسیرهای مطلق را نیز می‌توان ذکر کرد:
  "projects": [ "src", "test", "c:\\sources\\Configuration\\src" ],
اما در مورد هر مسیری که ذکر می‌شود، NET Core. باید بتواند یک سطح پایین‌تر از پوشه‌ی ذکر شده، فایل مهم project.json را پیدا کند؛ در غیراینصورت از آن صرفنظر خواهد شد. برای مثال برای مسیر نسبی src، مسیر src\MyProjectName\project.json را جستجو می‌کند و برای مسیر مطلق ذکر شده، این مسیر را c:\\sources\\Configuration\\src\\SomeName\\project.json


کامپایل خودکار پروژه در ASP.NET Core 1.0

علاوه بر تشخیص خودکار کم و زیاد شدن فایل‌های سیستمی پروژه، بدون نیاز به Add new item کردن آن‌ها در ویژوال استودیو، اگر سورس‌های برنامه را نیز تغییر دهید، فایل سورس جدیدی را اضافه کنید و یا فایل سورس موجودی را حذف کنید، کل پروژه به صورت خودکار کامپایل می‌شود و نیازی نیست این‌کار را به صورت دستی انجام دهید.
یک آزمایش: برنامه را از طریق منوی debug و گزینه‌ی start without debugging اجرا کنید. اگر برنامه را در حالت معمول debug->start debugging اجرا کنید، حالت کامپایل خودکار را مشاهده نخواهید کرد. در اینجا (پس از start without debugging) یک چنین خروجی را مشاهده خواهید کرد:


این خروجی حاصل اجرای کدهای درون فایل Startup.cs برنامه است:
 app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
اکنون در همین حال که برنامه در حال اجرا است و هنوز IIS Express خاتمه نیافته است، از طریق windows explorer، به پوشه‌ی src برنامه وارد شده و فایل Startup.cs را با notepad باز کنید. هدف این است که این فایل را در خارج از ویژوال استودیو ویرایش کنیم. اینبار سطر await دار را در notepad به نحو ذیل ویرایش کنید:
 await context.Response.WriteAsync("Hello DNT!");
پس از آن اگر مرورگر را refresh کنید، بلافاصله خروجی جدید فوق را مشاهده خواهید کرد که بیانگر کامپایل خودکار پروژه در صورت تغییر فایل‌های آن است.
این مساله قابلیت استفاده‌ی از ASP.NET Core را در سایر ادیتورهای موجود، مانند VSCode سهولت می‌بخشد.


نقش فایل project.json

فایل جدید project.json مهم‌ترین فایل تنظیمات یک پروژه‌ی ASP.NET Core است و مهم‌ترین قسمت آن، قسمت وابستگی‌های آن است:
"dependencies": {
  "Microsoft.NETCore.App": {
    "version": "1.0.0",
    "type": "platform"
  },
  "Microsoft.AspNetCore.Diagnostics": "1.0.0",
  "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
  "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
  "Microsoft.Extensions.Logging.Console": "1.0.0"
},
همانطور که در قسمت قبل نیز عنوان شد، در این نگارش از دات نت، تمام وابستگی‌های پروژه از طریق نیوگت تامین می‌شوند و دیگر خبری از یک دات نت «بزرگ» که شامل تمام اجزای این مجموعه‌است نیست. این مساله توزیع برنامه را ساده‌تر کرده و همچنین امکان به روز رسانی سریع‌تر این اجزا را توسط تیم‌های مرتبط فراهم می‌کند؛ بدون اینکه نیازی باشد تا منتظر یک توزیع «بزرگ» دیگر ماند.
در نگارش‌های قبلی ASP.NET، فایلی XML ایی به نام packages.config حاوی تعاریف مداخل بسته‌های نیوگت برنامه بود. این فایل در اینجا جزئی از محتوای فایل project.json در قسمت dependencies آن است.
با قسمت وابستگی‌های این فایل، به دو طریق می‌توان کار کرد:
الف) ویرایش مستقیم این فایل که به همراه intellisense نیز هست. با افزودن مداخل جدید به این فایل و ذخیره کردن آن، بلافاصله کار restore و دریافت و نصب آن‌ها آغاز می‌شود:


ب) از طریق NuGet package manager
روش دیگر کار با وابستگی‌ها، کلیک راست بر روی گره references و انتخاب گزینه‌ی manage nuget packages است:


برای نمونه جهت نصب ASP.NET MVC 6 این مراحل باید طی شوند:


ابتدا برگه‌ی browse را انتخاب کنید و سپس تیک مربوط به include prerelease را نیز انتخاب نمائید.
البته بسته‌ی اصلی MVC در اینجا Microsoft.AspNetCore.Mvc نام دارد و نه MVC6.

اینبار بسته‌هایی که restore می‌شوند، در مسیر اشتراکی C:\Users\user_name\.nuget\packages ذخیره خواهند شد.


یک نکته‌ی مهم:
قرار هست در نگارش‌های پس از RTM، فایل‌های project.json و xproj را جهت سازگاری با MSBuild، اندکی تغییر دهند (که این تغییرات به صورت خودکار توسط VS.NET انجام می‌شود). اطلاعات بیشتر


انتخاب فریم ورک‌های مختلف در فایل project.json

در قسمت قبل عنوان شد که ASP.NET Core را می‌توان هم برفراز NET Core. چندسکویی اجرا کرد و هم NET 4.6. مختص به ویندوز. این انتخاب‌ها در قسمت frameworks فایل project.json انجام می‌شوند:
"frameworks": {
  "netcoreapp1.0": {
    "imports": [
      "dotnet5.6",
      "portable-net45+win8"
    ]
  }
},
در اینجا، netcoreapp1.0 به معنای برنامه‌‌ای است که برفراز NET Core. اجرا می‌شود. نام پیشین آن dnxcore50 بود (اگر مقالات قدیمی‌تر پیش از RTM را مطالعه کنید).
در اینجا اگر علاقمند بودید که از دات نت کامل مخصوص ویندوز نیز استفاده کنید، می‌توانید آن‌را در لیست فریم ورک‌ها اضافه نمائید (در این مثال، دات نت کامل 4.5.2 نیز ذکر شده‌است):
 "frameworks": {
    "netcoreapp1.0": {
    },
    "net452": {
    }
  }
لیست کامل این اسامی را در مستندات NET Starndard می‌توانید مطالعه کنید و خلاصه‌ی آن به این صورت است:
- “netcoreapp1.0” برای معرفی و استفاده‌ی از NET Core 1.0. بکار می‌رود.
- جهت معرفی فریم ورک‌های کامل و ویندوزی دات نت، اسامی “net45”, “net451”, “net452”, “net46”, “net461” مجاز هستند.
-  “portable-net45+win8” برای معرفی پروفایل‌های PCL یا portable class libraries بکار می‌رود.
- “dotnet5.6”, “dnxcore50” برای معرفی نگارش‌های پیش نمایش NET Core.، پیش از ارائه‌ی نگارش RTM استفاده می‌شوند.
- “netstandard1.2”, “netstandard1.5” کار معرفی برنامه‌های NET Standard Platform. را انجام می‌دهند.

بر این مبنا، dotnet5.6 ذکر شده‌ی در قسمت تنظیمات نگارش RTM، به این معنا است که قادر به استفاده‌ی از بسته‌های نیوگت و کتابخانه‌های تولید شده‌ی با نگارش‌های RC نیز خواهید بود (هرچند برنامه از netcoreapp1.0 استفاده می‌کند).

یک مثال: قسمت فریم ورک‌های فایل project.json را به نحو ذیل جهت معرفی دات نت 4.6.1 تغییر دهید:
"frameworks": {
  "netcoreapp1.0": {
      "imports": [
          "dotnet5.6",
          "portable-net45+win8"
      ]
  },
  "net461": {
      "imports": [
          "portable-net45+win8"
      ],
      "dependencies": {
      }
  }
},
لیست وابستگی‌های خاص این فریم ورک در خاصیت dependencies آن قابل ذکر است.
در این حالت پس از ذخیره‌ی فایل و شروع خودکار بازیابی وابستگی‌ها، با پیام خطای Package Microsoft.NETCore.App 1.0.0 is not compatible with net461 متوقف خواهید شد.
برای رفع این مشکل باید وابستگی Microsoft.NETCore.App را حذف کنید، چون با net461 سازگاری ندارد
 "dependencies": {
 //"Microsoft.NETCore.App": {
 //  "version": "1.0.0",
 //  "type": "platform"
 //},

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


فایل project.lock.json چیست؟


ذیل فایل project.json، فایل دیگری به نام project.lock.json نیز وجود دارد. اگر به محتوای آن دقت کنید، این فایل حاوی لیست دقیق بسته‌های نیوگت مورد استفاده‌ی توسط برنامه است و الزاما با آن‌چیزی که در فایل project.json قید شده، یکی نیست. از این جهت که در فایل project.json، قید می‌شود netcoreapp1.0؛ ولی این netcoreapp1.0 دقیقا شامل چه بسته‌هایی است؟ لیست کامل آن‌ها را در این فایل می‌توانید مشاهده کنید.
در ابتدای این فایل یک خاصیت locked نیز وجود دارد که مقدار پیش فرض آن false است. اگر به true تنظیم شود، در حین restore وابستگی‌های برنامه، تنها از نگارش‌های ذکر شده‌ی در این فایل استفاده می‌شود. از این جهت که در فایل project.json می‌توان شماره نگارش‌ها را با * نیز مشخص کرد؛ مثلا *.1.0.0


پوشه‌ی جدید wwwroot و گره dependencies

یکی از پوشه‌های جدیدی که در ساختار پروژه‌ی ASP.NET Core معرفی شده‌است، wwwroot نام دارد:


از دیدگاه هاستینگ برنامه، این پوشه، پوشه‌ای است که در معرض دید عموم قرار می‌گیرد (وب روت). برای مثال فایل‌های ایستای اسکریپت، CSS، تصاویر و غیره باید در این پوشه قرار گیرند تا توسط دنیای خارج قابل دسترسی و استفاده شوند. بنابراین سورس کدهای برنامه خارج از این پوشه قرار می‌گیرند.
گره dependencies که ذیل پوشه‌ی wwwroot قرار گرفته‌است، جهت مدیریت این وابستگی‌های سمت کلاینت برنامه است. در اینجا می‌توان از NPM و یا Bower برای دریافت و به روز رسانی وابستگی‌های اسکریپتی و شیوه‌نامه‌های برنامه کمک گرفت (علاوه بر نیوگت که بهتر است صرفا جهت دریافت وابستگی‌های دات نتی استفاده شود).
یک مثال: فایل جدیدی را به نام bower.json به پروژه‌ی جاری با این محتوا اضافه کنید:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.6",
    "jquery": "2.2.0",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.6"
  }
}
این نام‌ها استاندارد هستند. برای مثال اگر قصد استفاده‌ی از npm مربوط به node.js را داشته باشید، نام این فایل packag.json است (با ساختار خاص خودش) و هر دوی این‌ها را نیز می‌توانید با هم اضافه کنید و از این لحاظ محدودیتی وجود ندارد.
پس از اضافه شدن فایل bower.json، بلافاصله کار restore بسته‌ها از اینترنت شروع می‌شود:


و یا با کلیک راست بر روی گره dependencies، گزینه‌ی restore packages نیز وجود دارد.
فایل‌های نهایی دریافت شده را در پوشه‌ی bower_components خارج از wwwroot می‌توانید مشاهده کنید.

در مورد نحوه‌ی توزیع و دسترسی به فایل‌های استاتیک یک برنامه‌ی ASP.NET Core 1.0، نکات خاصی وجود دارند که در قسمت‌های بعد، بررسی خواهند شد.

یک نکته: اگر خواستید نام پوشه‌ی wwwroot را تغییر دهید، فایل جدیدی را به نام hosting.json با این محتوا به پروژه اضافه کنید:
{
    "webroot":"AppWebRoot"
}
در اینجا AppWebRoot نام دلخواه پوشه‌ی جدیدی است که نیاز است به ریشه‌ی پروژه اضافه نمائید تا بجای wwwroot پیش فرض استفاده شود.


نقطه‌ی آغازین برنامه کجاست؟

اگر به فایل project.json دقت کنید، چنین تنظیمی در آن موجود است:
"buildOptions": {
  "emitEntryPoint": true,
  "preserveCompilationContext": true
},
true بودن مقدار تولید entry point به استفاده‌ی از فایلی به نام Program.cs بر می‌گردد؛ با این محتوا:
public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();
 
    host.Run();
}
به این ترتیب، یک برنامه‌ی ASP.NET Core، دقیقا همانند سایر برنامه‌های NET Core. رفتار می‌کند و در اساس یک برنامه‌ی کنسول است.
 var outputKind = compilerOptions.EmitEntryPoint.GetValueOrDefault() ?
OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary;
این چند سطر، قسمتی از سورس کد ASP.NET Core 1.0 هستند. به این معنا که اگر مقدار خاصیت emitEntryPoint مساوی true بود، با این برنامه به شکل یک برنامه‌ی کنسول رفتار کن و در غیر اینصورت یک Dynamically Linked Library خواهد بود.
در فایل Program.cs تنظیماتی را مشاهده می‌کنید، در مورد راه اندازی Kestler که وب سرور بسیار سریع و چندسکویی کار با برنامه‌های ASP.NET Core 1.0 است و قسمت مهم دیگر آن به استفاده‌ی از کلاس Startup بر می‌گردد (()<UseStartup<Startup). این کلاس را در فایل جدید Startup.cs می‌توانید ملاحظه کنید که کار تنظیمات آغازین برنامه را انجام می‌دهد. اگر پیشتر با OWIN، در نگارش‌های قبلی ASP.NET کار کرده باشید، قسمتی از این فایل برای شما آشنا است:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
در اینجا امکان دسترسی به تنظیمات برنامه، معرفی سرویس‌ها، middleware‌ها و غیره وجود دارند که هرکدام، در قسمت‌های آتی به تفصیل بررسی خواهند شد.


نقش فایل launchsetting.json


محتویات پیش فرض این فایل برای قالب empty پروژه‌های ASP.NET Core 1.0 به صورت ذیل است:
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:7742/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Core1RtmEmptyTest": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
همانطور که مشاهده می‌کنید، در اینجا تنظیمات IIS Express قابل تغییر هستند. برای مثال port پیش فرض برنامه 7742 تنظیم شده‌است.
پروفایل‌هایی که در اینجا ذکر شده‌اند، در تنظیمات پروژه نیز قابل مشاهده هستند: (کلیک راست بر روی پروژه و مشاهده‌ی properties آن و یا دوبار کلیک بر روی گره properties)


به علاوه امکان انتخاب این پروفایل‌ها در زمان آغاز برنامه نیز وجود دارند:


نکته‌ی مهم تمام این موارد به قسمت environment variable قابل مشاهده‌ی در تصاویر فوق بر می‌گردد. این متغیر محیطی می‌تواند سه مقدار Development ، Staging و Production را داشته باشد و بر اساس این متغیر و مقدار آن، می‌توان پروفایل جدیدی را تشکیل داد. زمانیکه برنامه بر اساس پروفایل خاصی بارگذاری می‌شود، اینکه چه متغیر محیطی انتخاب شده‌است، در کلاس Startup قابل استخراج و بررسی بوده و بر این اساس می‌توان اقدامات خاصی را انجام داد. برای مثال تنظیمات خاصی را بارگذاری کرد و یا صفحات ویژه‌ای را فعال و غیرفعال کرد (این موارد را در قسمت‌های بعدی مرور می‌کنیم).
همچنین در اینجا به ازای هر پروفایل مختلف می‌توان Url آغازین یا launch url و پورت آن‌را مجزا درنظر گرفت و یا از وب سرور دیگری استفاده کرد.
اشتراک‌ها
MariaDB جایگزین توسعه یافته MySql
پس از خریداری MySql توسط اوراکل دیگر قصد توسعه آن را ندارد و به جای آن MariaDB را به صورت سورس باز توسعه خواهد داد.
MariaDB جایگزین توسعه یافته MySql
نظرات اشتراک‌ها
HTML Helper برای jqGrid
اگر این کتابخانه‌ها نبودند و امروزه تا این حد تکامل پیدا نکرده بودند، هیچ وقت سیلورلایت و فلش از صحنه روزگار محو نمی‌شدند. در بسیاری از اوقات هدف ساخت یک برنامه غنی وب است. برنامه‌ای که تجربه کاربری آن همانند برنامه‌های دسکتاپ باشد. مثلا صفحه مدام چشمک نمی‌زند و کاربر احساس نمی‌کند که مدام رفت و برگشت به سرور وجود دارد، کنترل‌های auto-complete مانند برنامه‌های دسکتاپ و یا حتی شکیل‌تر و غنی‌تر از آن‌ها در وب وجود دارند، برنامه‌های تک صفحه‌ای وب، اعتبار سنجی‌های سمت کاربر، بدون هدایت او به سرور و دریافت پاسخ آنی و بسیاری از مثال‌های دیگری که در جهت بهبود کاربری در وب مطرح هستند. در اینجا HTML خالص بدون جاوا اسکریپت حرفی برای گفتن ندارد. HTML خالص صرفا جهت ارائه محتوا اختراع شده است نه طراحی برنامه‌های غنی وب و کار ما هم با ASP.NET و یا فناوری‌های مشابه، تولید برنامه‌های وب است و نه صرفا ارائه محتوا. اگر قرار است مثلا چند صفحه توضیح مشخصات شرکتی را در اینترنت قرار دهید، این تنها به معنای ارائه‌ی محتوا است. نیازی به برنامه خاصی ندارد. چند صفحه HTML به همراه مقداری هم CSS برای اینکار کافی است. اما اگر همینجا قرار شد از کاربر اطلاعات دریافت کنید، نیاز است تجربه کاربری او را غنی کنید.
نظرات مطالب
خلاصه‌ای کوتاه در مورد WinRT
معنای WinRT برای برنامه نویس‌های دات نت چیست؟

WinRT ، دات نت نیست. برای درک این موضوع باید CLR و دات نت فریم ورک را از هم جدا کرد. برنامه‌های دات نت نوشته شده برای WinRT برفراز CLR اجرا می‌شوند اما از دات نت فریم ورک استفاده نمی‌کنند. بجای آن از توانمندی‌های مشابه موجود در WinRT ، در پشت صحنه استفاده خواهند کرد.
در اینجا برنامه‌های C++ + XAML بدون دخالت CLR و مستقیما برفراز WinRT کار خواهند کرد. برنامه‌های سبک مترو HTML/CSS/JavaScript هم به همین صورت متکی به CLR‌ نیستند و مستقیما برفراز WinRT اجرا می‌شوند.

WinRT فقط قادر است برنامه‌های سبک مترو ویندوز 8 را هاست کند. اگر نیاز دارید سیستم عامل‌های قدیمی را پشتیبانی کنید یا اینکه اصلا کارتان ساخت برنامه‌های سمت کاربر و دسکتاپ نیست، اصلا این تغییرات به کار شما مرتبط نخواهند شد.

الگوی اصلی تعاملی برنامه‌های سبک مترو با تمرکز بر برنامه‌های لمسی (touch focused) و مبتنی بر محتوا (content-oriented) است. به این معنا که تمام برنامه‌های تجاری موجود که دارای 10 ها و صدها صفحه‌ی ورود اطلاعات هستند، اصلا برای این نوع سبک ارائه محتوا طراحی نشده‌اند و نخواهند شد. کاربردهای مهم این سبک، استفاده از آن‌ در برنامه‌های مخصوص تمام صفحه tablets است یا حداکثر در حد داشبرد‌های ارائه خلاصه گزارشات یک برنامه می‌توانند اهمیت داشته باشند. بنابراین اگر کارتان در حیطه‌ی ساخت برنامه‌های مخصوص tablets و برنامه‌های لمسی قرار نمی‌گیرد، کماکان به ساخت برنامه‌های دسکتاپ نوشته شده با WPF/WinForms می‌توانید مشغول باشید.

 انتقال قسمت عمده‌ای از برنامه‌های موجودWPF  و یا  Silverlight مبتنی بر الگوهایی مانند MVVM و امثال آن با توجه به عدم گره خوردگی آن‌ها به لایه نمایشی ، به WinRT میسر است.

در سمت سرور، تمرکز اصلی هنوز همان دات نت فریم ورک است. قرار نیست ASP.NET یا WCF و سایر مؤلفه‌های اصلی دات نت به WinRT منتقل شوند. حتی اگر WinRT به سرورهای بعدی هم راه پیدا کند در حد همان لایه نمایشی مترو است و تاثیری بر روی سرویس‌های NT با دسترسی بالای ویندوز ، نخواهد داشت. مثلا قرار نیست SQL Server را با WinRT پیاده سازی کنند.
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت اول - معرفی
مستندسازی یک API، مهم است. اگر این API عمومی باشد، نیاز به مستندات ویژه‌ی آن، امری بدیهی است و اگر عمومی نباشد، باز هم باید دارای مستندات باشد؛ از این جهت که سایر توسعه دهنده‌های یک تیم بتوانند به سادگی از آن استفاده کنند و همچنین نیازی نباشد تا یک سؤال مشخص را بارها پاسخ داد. به علاوه عدم وجود مستندات کافی در مورد یک API سبب خواهد شد تا سایر توسعه دهنده‌ها تصور کنند قابلیتی که مدنظرشان است هنوز پیاده سازی نشده‌است و خودشان شروع به توسعه‌ی آن کنند که سبب اتلاف وقت و هزینه خواهد شد. با استفاده از Swagger که «سوواَگِر» تلفظ می‌شود و یا OpenAI می‌توان این مشکل را برطرف کرد که ارائه دهنده‌ی توضیحی استاندارد از API شما می‌باشد. توسط آن می‌توان قابلیت‌های یک API را دریافت و یا نحوه‌ی تعامل با آن‌را از طریق HTTP، بررسی کرد. چون خروجی آن استاندارد است و مبتنی بر JSON و یا YAML می‌باشد، ابزارهایی نیز برای آن تهیه شده‌اند که برای نمونه می‌توانند بر مبنای آن، یک UI را طراحی و ارائه کنند. البته این ابزارهای ثالث صرفا محدود به تولید UI مستندات مبتنی بر OpenAPI نیستند، بلکه امکان تولید کد و یا آزمون‌های واحد نیز توسط آن‌ها وجود دارد.
به OpenAPI Specification عبارت Swagger Specification نیز گفته می‌شود؛ اما OpenAPI عبارتی است که باید ترجیح داده شود.


OpenAPI و Swagger

تا اینجا دریافتیم که استاندارد OpenAPI و یا Swagger، صرفا دو واژه برای انجام کاری مشابه هستند. اما در عمل Swagger تشکیل شده‌است از تعدادی ابزار سورس باز که برفراز استاندارد OpenAPI کار کرده و قابلیت‌هایی را ارائه می‌دهند؛ مانند Swagger Editor (برای تولید استاندارد)، Swagger UI (برای تولید UI مستندات)، Swagger CodeGen (برای تولید کدهای خودکار) و غیره. برای نمونه swagger-ui را می‌توانید در مخزن کد GitHub آن ملاحظه کنید.
البته این ابزارها به صورت عمومی تهیه شده‌اند. به همین جهت محصور کننده‌هایی نیز برای آن‌ها جهت یکپارچگی با ASP.NET Core، طراحی شده‌اند؛ مانند میان‌افزار Swashbuckle.AspNetCore. کار آن تولید OpenAPI Specification بر اساس ASP.NET Core 2x API شما می‌باشد که جزئیات انجام اینکار را به مرور بررسی خواهیم کرد. این کامپوننت به همراه یک swagger-ui جایگذاری شده (embedded) نیز می‌باشد که کار تهیه‌ی یک UI خودکار را بر اساس این استاندارد میسر می‌کند.
البته محصورکننده‌های متعددی برای ابزارهای Swagger، برای پروژه‌های ASP.NET Core وجود دارند که یکی دیگر از آن‌ها NSwag است. هرچند Swashbuckle.AspNetCore پرکاربردترین مورد تا به امروز بوده‌است.


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


در اینجا ساختار برنامه‌ی ASP.NET Core 2.2x نمونه‌ی این سری را ملاحظه می‌کنید که هدف اصلی آن، ارائه‌ی دو کنترلر Web API برای کتاب‌ها و نویسنده‌های آن‌ها می‌باشد:


برای نمونه اگر برنامه را اجرا کنید، در مسیر https://localhost:5001/api/authors، لیست تمام نویسندگانی که به صورت اطلاعات آغازین برنامه، توسط OpenAPISwaggerDoc.DataLayer ثبت شده‌اند، قابل مشاهده‌است:


و یا کتاب‌های نویسنده‌ای خاص را در آدرسی مانند https://localhost:5001/api/authors/2902b665-1190-4c70-9915-b9c2d7680450/books می‌توان مشاهده کرد:



کدهای کامل آغازین این نمونه برنامه را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-01.zip و شامل این اجزا است:
- OpenAPISwaggerDoc.Entities: به همراه موجودیت‌های نویسنده و کتاب است.
- OpenAPISwaggerDoc.DataLayer: شامل DbContext برنامه است؛ به همراه تعدادی رکورد پیش‌فرض جهت آغاز بانک اطلاعاتی و چون DbContext را در یک پروژه‌ی مجزا قرار داده‌ایم، نیاز به IDesignTimeDbContextFactory نیز دارد که این مورد هم در این پروژه لحاظ شده‌است.
- OpenAPISwaggerDoc.Models: شامل DTOهای برنامه است. برای نگاشت این DTOها به موجودیت‌ها و برعکس، از AutoMapper استفاده شده‌است که کار این نگاشت‌ها و تعریف پروفایل متناظر با آن‌ها، در پروژه‌ی OpenAPISwaggerDoc.Profiles صورت می‌گیرد.
- OpenAPISwaggerDoc.Services: کار استفاده‌ی از لایه‌ی OpenAPISwaggerDoc.DataLayer را جهت دسترسی به بانک اطلاعاتی و کار با موجودیت‌های کتاب‌ها و نویسندگان، انجام می‌دهد. از این سرویس‌ها در دو کنترلر Web API برنامه، برای دریافت اطلاعات نویسندگان و کتاب‌های آن‌ها از بانک اطلاعاتی، استفاده می‌شود.
- OpenAPISwaggerDoc.Web: پروژه‌ی اصلی است که دو کنترلر Web API را هاست می‌کند و تنظیمات اولیه‌ی این سرویس‌ها را در کلاس Startup انجام داده و همچنین کار اعمال خودکار Migrations را نیز در کلاس Program (بالاترین سطح برنامه) تکمیل می‌کند. رشته‌ی اتصالی این برنامه، در فایل appsettings.json تعریف شده‌است و به یک بانک اطلاعاتی LocalDB اشاره می‌کند که پس از اجرای برنامه به صورت خودکار ساخته شده و مقدار دهی می‌شود.


در قسمت بعد، کار مستند سازی این API را شروع می‌کنیم.