مطالب
بررسی Bad code smell ها: الگوی Shotgun Surgery
برای مشاهده طبقه بندی Bad code smell‌ها می‌توانید به اینجا مراجعه کنید.
زمانیکه به ازای هر تغییر، نیاز باشد تغییرات کوچکی در تعداد کلاس‌های زیادی انجام شود، این بوی بد کد بوجود آمده است. این الگو از دسته بندی «جلوگیری کنندگان از تغییر» است. نام این دسته بندی به طور واضح گویای مشکلی است که این الگوی بد ایجاد می‌کند. 

چرا چنین بویی به راه می‌افتد؟ 

یکی از نشانه‌های وجود چنین الگوی بدی در کدها، مشاهده کدهای تکراریست. ریشه اصلی این بوی بد، پراکنده کردن مسئولیت‌ها در کلاس‌های مختلف است. مسئولیت‌هایی که بهتر بود در یک کلاس جمع شوند. معمولا برای رفع این بوی بد اقدام به جمع کردن مسئولیت‌ها از نقاط مختلف به یک کلاس می‌کنند.  
با توجه به توضیحات ارائه شده، این بوی بد عملا یکی از علایم اجرایی نکردن اصل Single responsibility  و Open closed از اصول طراحی شیء گرایی است. موارد دیگری که در ایجاد چنین مشکلی کمک می‌کنند به صورت زیر هستند:
  • استفاده نادرست از الگوهای طراحی شیء گرا 
  • عدم درک درست مسئولیت‌های کلاس‌های ایجاد شده 
  • عدم تشخیص مکانیزم‌های مشترک در کد و جداسازی مناسب آنها 
برای بررسی بیشتر این موضوع فرض کنید کلاس‌هایی در نرم افزار خود دارید که شماره تلفن کاربر را به صورت ورودی دریافت و روی آن کار خاصی را انجام می‌دهند. در ابتدای تولید نرم افزار فرمت صحیح شماره تلفن به صورت "04135419999" تشخیص داده شده است و مکانیزم اعتبارسنجی آن نیز با استفاده regular express‌ionها پیاده سازی شده‌است.  بعدا نیازمندی دیگری بوجود می‌آید که شماره تلفن‌هایی با کد بین المللی نیز در نرم افزار قابل استفاده باشند. مانند "984135410000+" دو نوع پیاده سازی (از میان روش‌های فراوان پیاده سازی) برای تشریح این موضوع می‌توان متصور بود. فرض کنید در دو موجودیت «کاربر» و «آدرس» نیاز به ذخیره سازی شماره تلفن وجود دارد. 

اول: هر جائیکه نیاز به اعتبارسنجی شماره تلفن وجود داشته باشد؛ این کار تماما در همان مکان انجام شود.
public class UserService 
{ 
        public void SaveUser(dynamic userEntity) { 
            var regEx = "blablabla"; 
            var phoneIsValid = Regex.IsMatch(userEntity.PhoneNumber, regEx); 
            if (!phoneIsValid) 
                return; 
            // ... 
        } 
}  

public class AddressService 
{ 
        public void SaveAddress(dynamic addressEntity) 
        { 
            var regEx = "blablabla"; 
            var phoneIsValid = Regex.IsMatch(addressEntity.PhoneNumber, regEx); 
            if (!phoneIsValid) 
                return; 
        } 
}
در این روش پیاده سازی اگر دقت کرده باشید روال مربوط به اعتبارسنجی در دو متد «ذخیره کاربر» و «ذخیره آدرس» تکرار شده‌است . این الگوی کد نویسی، علاوه بر این که خود نوعی بوی بد کد محسوب می‌شود، باعث ایجاد الگوی Shotgun surgery نیز است.  
در اینجا اگر قصد اعمال تغییری در منطق مربوط به اعتبارسنجی شماره تلفن وجود داشته باشد، نیاز خواهد بود تمامی مکان‌هایی که این منطق پیاده سازی شده‌است، بسته به شرایط جدید تغییر کند. یعنی برای تغییر یک منطق اعتبارسنجی نیاز خواهد بود کلاس‌های زیادی تغییر کنند.  

دوم: راه بهتر در انجام چنین کاری، جداسازی منطق مربوط به اعتبارسنجی شماره تلفن و انتقال آن به کلاسی جداگانه‌است؛ به صورت زیر: 
public class PhoneValidator
{ 
        public bool IsValid(string phoneNumber) 
        { 
            var regEx = "blablabla"; 
            var phoneIsValid = Regex.IsMatch(phoneNumber, regEx); 
            if (!phoneIsValid) 
                return false; 
            return true; 
        } 
 } 
 
public class UserService 
{ 
        public void SaveUser(dynamic userEntity) 
        { 
            var validator = new PhoneValidator(); 
            var phoneIsValid  = validator.IsValid(userEntity.PhoneNumber); 
            if (!phoneIsValid) 
                return; 
            // ... 
        } 
 } 
 
public class AddressService 
{ 
        public void SaveAddress(dynamic addressEntity) 
        { 
            var validator = new PhoneValidator(); 
            var phoneIsValid = validator.IsValid(addressEntity.PhoneNumber); 
            if (!phoneIsValid) 
                return; 
           // ... 
        } 
}

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

شکل دیگر 

شکل دیگر این بوی بد کد، Divergent Change است. با این تفاوت که در الگوی Divergent Change تغییرات در یک کلاس اتفاق می‌افتند نه در چندین کلاس به طور همزمان. 

جمع بندی

تشخیص چنین الگوی بد کد نویسی ای همیشه به این سادگی نیست. یکی از راه‌های تشخیص سریع چنین بوی بد کدی این است که به کارهای تکراری عادت نکنید! و زمانیکه متوجه شدید کار خاصی را در کد به صورت تکراری انجام می‌دهید، دقت لازم را برای تغییر آن داشته باشید؛ به صورتیکه نیاز به اعمال تغییرات تکراری در مکان‌های مختلف کد وجود نداشته باشد. راه دیگر زمانی است که کدی تکراری را مشاهده کردید. زمانیکه کدی تکراری در کدها وجود داشته باشد، اطمینان داشته باشید هنگام تغییر آن به این مشکل دچار خواهید شد. برای رفع موضوع کد تکراری می‌توانید از روش‌های مختلفی که عنوان شد استفاده کنید. 
مطالب
آموزش Prism #2
در پست قبلی توضیح کلی درباره فریم ورک Prism داده شد. در این بخش قصد داریم آموزش‌های داده شده در پست قبلی را با هم در یک مثال مشاهده کنیم. در پروژه‌های ماژولار طراحی و ایجاد زیر ساخت قوی برای مدیریت ماژول‌ها بسیار مهم است. Prism فریم ورکی است که فقط چارچوب و قواعد اصول طراحی این گونه پروژه‌ها را در اختیار ما قرار می‌دهد. در پروژه‌های ماژولار هر ماژول باید در یک اسمبلی جدا قرار داشته باشد که ساختار پیاده سازی آن می‌تواند کاملا متفاوت با پیاده سازی سایر ماژول‌ها باشد.
 برای شروع  باید فایل‌های اسمبلی Prism رو دانلود کنید(لینک دانلود).
تشریح پروژه:
می‌خواهیم برنامه ای بنویسیم که دارای سه ماژول زیر است.:
  1. ماژول Navigator : برای انتخاب و Switch کردن بین ماژول‌ها استفاده می‌شود؛
  2. ماژول طبقه بندی کتاب‌ها : لیست طبقه بندی کتاب‌ها را به ما نمایش می‌دهد؛
  3. ماژول لیست کتاب‌ها : عناوین کتاب‌ها به همراه نویسنده و کد کتاب را به ما نمایش می‌دهد.

*در این پروژه از UnityContainer برای مباحث Dependency Injection استفاده شده است.
ابتدا یک پروژه WPF در Vs.Net ایجاد کنید(در اینجا من نام آن را  FirstPrismSample گذاشتم). قصد داریم یک صفحه طراحی کنیم که دو ماژول مختلف در آن لود شود. ابتدا باید Shell پروژه رو طراحی کنیم. یک Window جدید به نام Shell بسازید و کد زیر را در آن کپی کنید.
<Window x:Class="FirstPrismSample.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:com="http://www.codeplex.com/CompositeWPF"
    Title="Prism Sample By Masoud Pakdel" Height="400" Width="600" WindowStartupLocation="CenterScreen">
    <DockPanel>
      <ContentControl com:RegionManager.RegionName="WorkspaceRegion" Width="400"/>
      <ContentControl com:RegionManager.RegionName="NavigatorRegion"  DockPanel.Dock="Left" Width="200" />     
    </DockPanel>
</Window>
در این صفحه دو ContentControl تعریف کردم یکی به نام Navigator و دیگری به نام Workspace. به وسیله RegionName که یک AttachedProperty است هر کدوم از این نواحی را برای Prism تعریف کردیم. حال باید یک ماژول برای Navigator و دو ماژول دیگر یکی برای طبقه بندی کتاب‌ها و دیگری برای لیست کتاب‌ها بسازیم.

#پروژه Common
قبل از هر چیز یک پروژه Common می‌سازیم و مشترکات بین ماژول‌ها رو در آن قرار می‌دهیم(این پروژه باید به تمام ماژول‌ها رفرنس داده شود).  این مشترکات شامل :
  • کلاس پایه ViewModel
  • کلاس ViewRequestEvent
  • کلاس ModuleService

کد کلاس ViewModelBase که فقط اینترفیس INotifyPropertyChanged رو پیاده سازی کرده است:

using System.ComponentModel;

namespace FirstPrismSample.Common
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChangedEvent( string propertyName )
        {
            if ( PropertyChanged != null )
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs( propertyName );
                PropertyChanged( this, e );
            }
        }
    }
}
کلاس ViewRequestEvent که به صورت زیر است:
using Microsoft.Practices.Composite.Presentation.Events;

namespace FirstPrismSample.Common.Events
{
    public class ViewRequestedEvent : CompositePresentationEvent<string>
    {
    }
}
توضیح درباره CompositePresentationEvent :
در طراحی و توسعه پروژه‌های ماژولار نکته ای که باید به آن دقت کنید این است که ماژول‌های پروژه نباید به هم وابستگی مستقیم داشته باشند در عین حال ماژول‌ها باید بتوانند با هم در ارتباط باشند. CPE یا CompositePresentationEventدقیقا برای این منظور به وجود آمده است. CPE که در این جا طراحی کردم فقط کلاسی است که از CompositePresentationEventارث برده است و دلیل آن که به صورت string generic استفاده شده است این است که می‌خواهیم در هر درخواست نام ماژول درخواستی را داشته باشیم و به همین دلیل نام آن را ViewRequestedEvent گذاشتم.

توضیح درباره EventAggregator

EventAggregator یا به اختصار EA مکانیزمی است در پروژهای ماژولار برای اینکه در Composite UI‌ها بتوانیم بین کامپوننت‌ها ارتباط برقرار کنیم. استفاده از EA وابستگی بین ماژول‌ها را  از بین خواهد برد. برنامه نویسانی که با MVVM Light آشنایی دارند از قابلیت Messaging موجود در این فریم ورک برای ارتباط بین View و  ViewModel استفاده می‌کنند. در Prism این عملیات توسط EA انجام می‌شود. یعنی برای ارتباط با View‌ها باید از EA تعبیه شده در Prism استفاده کنیم. در ادامه مطلب، چگونگی استفاده از EA را خواهید آموخت.
اینترفیس IModuleService که فقط شامل یک متد است:
namespace FirstPrismSample .Common
{
    public interface IModuleServices
    {     
        void ActivateView(string viewName);
    }
}
کلاس ModuleService که اینترفیس بالا را پیاده سازی کرده است:
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;

namespace FirstPrismSample.Common
{
    public class ModuleServices : IModuleServices
    {     
        private readonly IUnityContainer m_Container;  
     
        public ModuleServices(IUnityContainer container)
        {
            m_Container = container;
        }      
   
        public void ActivateView(string viewName)
        {        
            var regionManager = m_Container.Resolve<IRegionManager>();

            // غیر فعال کردن ویو
            IRegion workspaceRegion = regionManager.Regions["WorkspaceRegion"];
            var views = workspaceRegion.Views;
            foreach (var view in views)
            {
                workspaceRegion.Deactivate(view);
            }

            //فعال کردن ویو انتخاب شده 
            var viewToActivate = regionManager.Regions["WorkspaceRegion"].GetView(viewName);
            regionManager.Regions["WorkspaceRegion"].Activate(viewToActivate);
        }
    }
}
متد ActivateView نام view مورد نظر برای فعال سازی را دریافت می‌کند. برای فعال کردن View ابتدا باید سایر view‌های فعال در RegionManager را غیر فعال کنیم. سپس فقط view مورد نظر در RegionManager انتخاب و فعال می‌شود.

*نکته: در هر ماژول ارجاع به اسمبلی‌های Prism مورد نیاز است.

#ماژول طبقه بندی کتاب ها:
برای شروع یک Class Library جدید به نام ModuleCategory به پروژه اضافه کنید. یک UserControl به نام CategoryView بسازید و کد‌های زیر را در آن کپی کنید.
<UserControl x:Class="FirstPrismSample.ModuleCategory.CategoryView "
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             Background="LightGray" FlowDirection="RightToLeft" FontFamily="Tahoma">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Text=" طبقه بندی ها"/>
        <ListView Grid.Row="1"  Margin="10" Name="lvCategory">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="کد" Width="50" />
                    <GridViewColumn Header="عنوان" Width="200"  />                  
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>
یک کلاس به نام CategoryModule بسازید که اینترفیس IModule رو پیاده سازی کند.
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;
using FirstPrismSample.Common;
using FirstPrismSample.Common.Events;
using Microsoft.Practices.Composite.Presentation.Events;

namespace FirstPrismSample.ModuleCategory
{
    [Module(ModuleName = "ModuleCategory")]
    public class CategoryModule : IModule
    {      
        private readonly IUnityContainer m_Container;
        private readonly string moduleName = "ModuleCategory";
            
        public CategoryModule(IUnityContainer container)
        {
            m_Container = container;
        }   
      
        ~CategoryModule()
        {
            var eventAggregator = m_Container.Resolve<IEventAggregator>();
            var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();       
            viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler);
        }
     
        public void Initialize()
        {           
            var regionManager = m_Container.Resolve<IRegionManager>();
            regionManager.Regions["WorkspaceRegion"].Add(new CategoryView(), moduleName);
         
            var eventAggregator = m_Container.Resolve<IEventAggregator>();
            var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
            viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true);
        }
       
        public void ViewRequestedEventHandler(string moduleName)
        {
            if (this.moduleName != moduleName) return;
          
            var moduleServices = m_Container.Resolve<IModuleServices>();
            moduleServices.ActivateView(moduleName);
        }      
    }
}
چند نکته :
*ModuleAttribute استفاده شده در بالای کلاس برای تعیین نام ماژول استفاده می‌شود. این Attribute دارای دو خاصیت دیگر هم است :
  1. OnDemand : برای تعیین اینکه ماژول باید به صورت OnDemand (بنا به درخواست) لود شود.
  2. StartupLoaded : برای تعیین اینکه ماژول به عنوان ماژول اول پروزه لود شود.(البته این گزینه Obsolute شده است)

*برای تعریف ماژول کلاس مورد نظر حتما باید اینترفیس IModule را پیاده سازی کند. این اینترفیس فقط شامل یک متد است به نام Initialize.

*در این پروژه چون View‌های برنامه صرفا جهت نمایش هستند در نتیجه نیاز به ایجاد ViewModel برای آن‌ها نیست. در پروژه‌های اجرایی حتما برای هر View باید ViewModel متناظر با آن تهیه شود.

توضیح درباره متد Initialize

در این متد ابتدا با استفاده از Container موجود RegionManager را به دست می‌آوریم. با استفاده از RegionManager می‌تونیم یک CompositeUI طراحی کنیم. در فایل Shell مشاهده کردید که یک صفحه به دو ناحیه تقسیم شد و به هر ناحیه هم یک نام اختصاص دادیم. دستور زیر به یک ناحیه اشاره خواهد داشت:

regionManager.Regions["WorkspaceRegion"]
در خط بعد با استفاده از EA یا Event Aggregator توانستیم CPE را بدست بیاوریم. متد Subscribe در کلاس CPE  یک ارجاع قوی به delegate مورد نظر ایجاد می‌کند(پارامتر دوم این متد که از نوع boolean است) که به این معنی است که این delegate هیچ گاه توسط GC جمع آوری نخواهد شد. در نتیجه، قبل از اینکه ماژول بسته شود باید به صورت دستی این کار را انجام دهیم که مخرب را برای همین ایجاد کردیم. اگر به کد‌های مخرب دقت کنید می‌بینید که با استفاده از EA توانستیم ViewRequestEventHandler را Unsubscribe کنیم به دلیل اینکه از ارجاع قوی با strong Reference در متد Subscribe استفاده شده است.
دستور moduleService.ActiveateView ماژول مورد نظر را در region مورد نظر هاست خواهد کرد.

#ماژول لیست کتاب ها:
ابتدا یک Class Library به نام ModuleBook بسازید  و همانند ماژول قبلی نیاز به یک Window و یک کلاس داریم:
BookWindow که کاملا مشابه به CategoryView است.
<UserControl x:Class="FirstPrismSample.ModuleBook.BookView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Background="LightGray" FontFamily="Tahoma" FlowDirection="RightToLeft">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Text="لیست کتاب ها"/>
        <ListView Grid.Row="1" Margin="10" Name="lvBook">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="کد" Width="50"  />
                    <GridViewColumn Header="عنوان" Width="200" />
                    <GridViewColumn Header="نویسنده" Width="150" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

کلاس BookModule که پیاده سازی  و توضیحات آن کاملا مشابه به CategoryModule می‌باشد.
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Presentation.Events;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Unity;
using FirstPrismSample.Common;
using FirstPrismSample.Common.Events;

namespace FirstPrismSample.ModuleBook
{
    [Module(ModuleName = "moduleBook")]
    public class BookModule : IModule
    {      
        private readonly IUnityContainer m_Container;
        private readonly string moduleName = "ModuleBook";     
    
        public BookModule(IUnityContainer container)
        {
            m_Container = container;          
        }     
       
        ~BookModule()
        {           
            var eventAggregator = m_Container.Resolve<IEventAggregator>();
            var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
          
            viewRequestedEvent.Unsubscribe(ViewRequestedEventHandler);
        }     
     
        public void Initialize()
        {           
            var regionManager = m_Container.Resolve<IRegionManager>();
            var view = new BookView();
            regionManager.Regions["WorkspaceRegion"].Add(view, moduleName);
            regionManager.Regions["WorkspaceRegion"].Deactivate(view);
      
            var eventAggregator = m_Container.Resolve<IEventAggregator>();
            var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
            viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, true);
        }     
      
        public void ViewRequestedEventHandler(string moduleName)
        {           
            if (this.moduleName != moduleName) return;
         
            var moduleServices = m_Container.Resolve<IModuleServices>();
            moduleServices.ActivateView(m_WorkspaceBName);
        }
    }
}
#ماژول Navigator
برای این ماژول هم ابتدا View مورد نظر را ایجاد می‌کنیم:
<UserControl x:Class="FirstPrismSample.ModuleNavigator.NavigatorView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Grid>
        <StackPanel VerticalAlignment="Center">
            <TextBlock Text="انتخاب ماژول" Foreground="Green" HorizontalAlignment="Center"
                VerticalAlignment="Center" FontFamily="Tahoma" FontSize="24" FontWeight="Bold" />
        <Button Command="{Binding ShowModuleCategory}" Margin="5" Width="125">طبقه بندی کتاب ها</Button>
        <Button Command="{Binding ShowModuleBook}" Margin="5" Width="125">لیست کتاب ها</Button>
        </StackPanel>
        </Grid>
</UserControl>
حال قصد داریم برای این View یک ViewModel بسازیم. نام آن را INavigatorViewModel خواهیم گذاشت:
public interface INavigatorViewModel
    {    
        ICommand ShowModuleCategory { get; set; }       
        ICommand ShowModuleBook { get; set; }

        string ActiveWorkspace { get; set; }       

        IUnityContainer Container { get; set; }

        event PropertyChangedEventHandler PropertyChanged;
    }
 *در اینترفیس بالا دو Command داریم که هر کدام وظیفه لود یک ماژول را بر عهده دارند.
 *خاصیت ActiveWorkspace برای تعیین workspace فعال تعریف شده است.

حال به پیاده سازی مثال بالا می‌پردازیم:
public class NavigatorViewModel : ViewModelBase, INavigatorViewModel
    {        
        public NavigatorViewModel(IUnityContainer container)
        {
            this.Initialize(container);
        }   
       
        public ICommand ShowModuleCategory { get; set; }
      
        public ICommand ShowModuleBook { get; set; }      
              
        public string ActiveWorkspace { get; set; }       

        public IUnityContainer Container { get; set; }        
     
        private void Initialize(IUnityContainer container)
        {
            this.Container = container;
            this.ShowModuleCategory = new ShowModuleCategoryCommand(this);
            this.ShowModuleBook = new ShowModuleBookCommand(this);
            this.ActiveWorkspace = "ModuleCategory";
        }        
    }
تنها نکته مهم در کلاس بالا متد Initialize است که دو Command مورد نظر را پیاده سازی کرده است. ماژول پیش فرض هم ماژول طبقه بندی کتاب‌ها یا ModuleCategory در نظر گرفته شده است.  همان طور که می‌بینید پیاده سازی Command‌ها بالا توسط دو کلاس ShowModuleCategoryCommand و ShowModuleBookCommand انجام شده که در زیر کد‌های آن‌ها را می‌بینید.
#کد کلاس ShowModuleCategoryCommand  
public class ShowModuleCategoryCommand : ICommand
    {      
        private readonly NavigatorViewModel viewModel;
        private const string workspaceName = "ModuleCategory";         

        public ShowModuleCategoryCommand(NavigatorViewModel viewModel)
        {
            this.viewModel = viewModel;
        }          

        public bool CanExecute(object parameter)
        {
            return viewModel.ActiveWorkspace != workspaceName;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
     
        public void Execute(object parameter)
        {
            CommandServices.ShowWorkspace(workspaceName, viewModel);
        }      
    }
#کد کلاس ShowModuleBookCommand  
public class ShowModuleBookCommand : ICommand
    {
        private readonly NavigatorViewModel viewModel;
        private readonly string workspaceName = "ModuleBook";

        public ShowModuleBookCommand( NavigatorViewModel viewModel )
        {
            this.viewModel = viewModel;
        }

        public bool CanExecute( object parameter )
        {
            return viewModel.ActiveWorkspace != workspaceName;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute( object parameter )
        {
            CommandServices.ShowWorkspace( workspaceName , viewModel );
        }
    }
با توجه به این که فرض است با متد‌های Execute و CanExecute و CanExecuteChanged آشنایی دارید از توضیح این مطالب خودداری خواهم کرد. فقط کلاس CommandServices  در متد Execute دارای متدی به نام ShowWorkspace است که کد‌های زیر را شامل می‌شود:
public static void ShowWorkspace(string workspaceName, INavigatorViewModel viewModel)
  {           
            var eventAggregator = viewModel.Container.Resolve<IEventAggregator>();
            var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
            viewRequestedEvent.Publish(workspaceName);
        
            viewModel.ActiveWorkspace = workspaceName;
 }
در این متد با استفاده از CPE که در پروژه Common ایجاد کردیم ماژول مورد نظر را لود خواهیم کرد. و بعد از آن مقدار ActiveWorkspace جاری در ViewModel به نام ماژول تغییر پیدا می‌کند. متد Publish در CPE این کار را انجام خواهد دارد.

عدم وابستگی ماژول ها
همان طور که می‌بینید ماژول‌های پروژه به هم Reference داده نشده اند حتی هیچ Reference هم به پروژه اصلی یعنی جایی که فایل App.xaml قرار دارد، داده نشده است ولی در عین حال باید با هم در ارتباط باشند. برای حل این مسئله این ماژول‌ها باید در فولدر bin پروژه اصلی خود را کپی کنند. بهترین روش استفاده از Pre-Post Build Event خود VS.Net است. برای این کار از پنجره Project Properties وارد برگه Build Events شوید و از قسمت Post Build Event Command Line  استفاده کنید و کد زیر را در آن کپی نمایید:
xcopy "$(TargetDir)FirstPrismSample.ModuleBook.dll" "$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
قطعا باید به جای FirstPrismSample نام Solution خود و به جای ModuleBook نام ماژول را وارد نمایید.

مانند:


مراحل بالا برای هر ماژول باید تکرار شود(ModuleNavigation , ModuleBook , ModuleCategory). بعد از Rebuild  پروژه در فولدر bin پروژه اصلی یک فولدر به نام Module ایجاد می‌شود که اسمبلی هر ماژول در آن کپی خواهد شد.

ایجاد Bootstrapper
حال نوبت به Bootstrapper میرسد(در پست قبلی در باره مفهوم Bootstrapper شرح داده شد). در پروژه اصلی یعنی جایی که فایل App.xaml قرار دارد کلاس زیر را ایجاد کنید.
    public class Bootstrapper : UnityBootstrapper
    {     
        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();
            Container.RegisterType<IModuleServices, ModuleServices>();
        }
   
        protected override DependencyObject CreateShell()
        {
            var shell = new Shell();
            shell.Show();
            return shell;
        }
   
        protected override IModuleCatalog GetModuleCatalog()
        {           
            var catalog = new DirectoryModuleCatalog();
            catalog.ModulePath = @".\Modules";
            return catalog;
        }
    }
متد ConfigureContainer برای تزریق وابستگی به وسیله UnityContainer استفاده می‌شود. در این متد باید تمامی Registration‌های مورد نیاز برای DI را انجام دهید. نکته مهم این است که عملیات وهله سازی و Initialization برای  Container  در متد base کلاس UnityBootstrapper انجام خواهد شد پس همیشه باید متد base این کلاس در ابتدای این متد فراخوانی شود در غیر این صورت با خطا متوقف خواهید شد.
متد CreateShell برای ایجاد و وهله سازی از Shell پروژه استفاده می‌شود. در این جا یک وهله از Shell Window برگشت داده می‌شود.
متد GetModuleCatalog برای تعیین مسیر ماژول‌ها در پروژه کاربرد دارد. در این متد با استفاده از خاصیت ModulePath کلاس DirectoryModuleCatalog تعیین کرده ایم که ماژول‌های پروژه در فولدر Modules موجود در bin اصلی پروژه قرار دارد. اگر به دستورات کپی در Post Build Event قسمت قبل توجه کنید می‌بینید که دستور ساخت فولدر وجود دارد.
"$(SolutionDir)FirstPrismSample\bin\$(ConfigurationName)\Modules\" /Y
*نکته: اگر استفاده از این روش برای شناسایی ماژول‌ها توسط Bootstrapper را چندان جالب نمی‌دانید می‌تونید از MEF استفاده کنید که اسمبلی ماژول‌های پروژه را به راحتی شناسایی می‌کند و در اختیار Bootsrtapper قرار می‌دهد(از آن جا در مستندات مربوط به Prism، بیشتر به استفاده از MEF تاکید شده است من هم در پست‌های بعدی، مثال‌ها را با MEF پیاده سازی خواهم کرد)

در پایان باید فایل App.xaml را تغییر دهید به گونه ای که متد Run در کلاس Bootstapper ابتدا اجرا شود.
public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var bootstrapper = new Bootstrapper();
            bootstrapper.Run();
        }
    }


اجرای پروژه:
بعد از اجرا، با انتخاب ماژول مورد نظر اطلاعات ماژول در Workspace Content Control لود خواهد شد.

ادامه دارد...


مطالب
کار با Areas در ASP.NET Core
کار با Areas را تا ASP.NET MVC 5.x می‌توانید در مطلب «ASP.NET MVC #14» مطالعه کنید. در ASP.NET Core، کلیات آن ثابت مانده‌است و تنظیمات ابتدایی آن اندکی تغییر کرده‌اند.


مفهوم Areas

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


ایجاد Areas

اگر با ASP.NET MVC 5.x کار کرده باشید، می‌دانید که ویژوال استودیو با کلیک راست بر روی پروژه‌ی جاری، گزینه افزودن یک Area جدید را به همراه دارد. یک چنین قابلیتی تا ASP.NET Core 1.1 به ابزارهای همراه آن افزوده نشده‌است. بنابراین تمام مراحل ذیل را باید دستی ایجاد کنید و هنوز قالب از پیش تعریف شده و ساده کننده‌‌ای برای اینکار وجود ندارد:


همانطور که در تصویر نیز ملاحظه می‌کنید، نیاز است در ریشه‌ی پروژه، پوشه‌ی جدیدی را به نام Areas ایجاد کرد. سپس در داخل این پوشه می‌توان نواحی مختلفی را با پوشه بندی‌های مجزایی ایجاد نمود. برای مثال در اینجا ناحیه‌ی Blog ایجاد شده‌است که در این ناحیه نیز پوشه‌های Controllers و Views آن باید به صورت دستی ایجاد شوند.


افزودن مسیریابی مرتبط با Areas

پس از اضافه کردن دستی پوشه‌های Areas و ناحیه‌ی جدید، به همراه ساختار پوشه‌های کنترلرها و Viewهای آن، اکنون نیاز است این ناحیه‌ی جدید را به سیستم مسیریابی معرفی نمود. برای این منظور به فایل آغازین برنامه مراجعه کرده و در متد Configure آن، تعریف جدید ذیل را اضافه می‌کنیم:
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "areas",
        template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
 
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}"); 
});
مسیریابی پیش‌فرض را در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 9 - بررسی تغییرات مسیریابی» پیشتر بررسی کرده‌ایم. در اینجا پیش از این مسیریابی، مسیریابی جدید areas تعریف شده‌است. قید exists در اینجا به معنای تنها استفاده‌ی از نواحی تعریف شده‌ی در برنامه‌ی جاری است و الگوی تعریف شده، تمام آن‌ها را شامل می‌شود و دیگر نیازی به تعریف مسیریابی جداگانه‌ای به ازای ایجاد هر Area جدید نیست (برخلاف ASP.NET MVC 5.x).


علامتگذاری کنترلرهای یک ناحیه

تمام اصول کار کردن با کنترلرهای یک ناحیه، مانند سایر کنترلرهای دیگر برنامه‌است؛ با یک تفاوت:
[Area("Blog")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}
در ASP.NET Core حتما نیاز است توسط ویژگی جدید Area، نام ناحیه‌ی کنترلر را صریحا مشخص کرد؛ در غیراینصورت، صرفنظر از محل تعریف این کنترلر، اطلاعات آن متعلق به هیچ ناحیه‌ای نبوده و وارد سیستم مسیریابی Areas نمی‌شود.

یک نکته: اگر از Attribute routing استفاده می‌کنید، توکن مرتبط با نواحی، [area] نام دارد:
 [Route("[area]/app/[controller]/actions/[action]/{id:weekday?}")]


فعالسازی Layout و Tag Helpers در Areas

اگر در همین حال، برنامه را اجرا و به مسیر http://localhost/blog مراجعه کنید، هرچند اطلاعات View متناظر با کنترلر Home و اکشن متد Index آن نمایش داده می‌شوند، اما این View نه layout دارد و نه Tag helpers آن پردازش شده‌اند. برای فعالسازی این دو مورد، دو فایل ViewStart.cshtml_ و ViewImports.cshtml_ را از پوشه‌ی views اصلی پروژه، به پوشه‌ی views این Area جدید کپی کنید. فایل ViewStart، نام و مسیر فایل layout پیش فرض ناحیه را مشخص می‌کند و فایل ViewImports حاوی تعاریف فعالسازی Tag helpers است:
 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
 هر Area می‌تواند layout خاص خودش را داشته باشد؛ اما اگر فایل ViewStart آن به نحو ذیل مقدار دهی شود، به فایل اصلی واقع در پوشه‌ی Views/Shared/_Layout.cshtml ریشه‌ی پروژه، اشاره می‌کند.
 @{
 Layout = "_Layout";
}
و برای تغییر و یا مقداردهی صریح آن می‌توان به صورت ذیل عمل کرد:
 @{
 Layout = "~/Areas/Blog/Views/Shared/_Layout.cshtml";
}


Areas و تاثیر آن‌ها در حین لینک دهی به قسمت‌های مختلف برنامه

اگر قرار است لینکی به قسمتی واقع در همان Area جاری مرتبط شود، نیازی نیست تا هیچ نکته‌ی خاصی را درنظر گرفت و تولید لینک‌ها به نحو صحیحی صورت می‌گیرند:
 <a asp-action="Index" asp-controller="Home">Link</a>
اما اگر می‌خواهیم به ناحیه‌ی جدیدی به نام Services و کنترلر و اکشن متد خاصی از آن، از یک ناحیه‌ی دیگر لینک دهیم، نیاز است asp-area را صریحا ذکر کرد:
 <a asp-area="Services" asp-controller="Home" asp-action="Index">Go to Services’ Home Page</a>
به علاوه  اگر قصد تعریف لینکی را به یک اکشن متد واقع در کنترلری که در هیچ ناحیه‌ای قرار ندارد، داشته باشیم، باید asp-area آن‌را خالی ذکر کنیم:
 <a asp-action="Index" asp-controller="Home" asp-area="">Link</a>


تاثیر Areas بر روی تنظیمات توزیع برنامه

فایل‌های View موجود در Areas نیز باید در حین توزیع نهایی برنامه ارائه شوند؛ مگر اینکه آن‌ها را از پیش کامپایل کرده باشیم. اگر از حالت از پیش کامپایل کردن Viewها استفاده نمی‌شود، نیاز است قسمت publishOptions فایل project.json را به نحو ذیل در جهت الحاق فایل‌های Viewهای نواحی مختلف، ویرایش و تکمیل کرد:
"publishOptions": {
     "include": [
       "Areas/**/*.cshtml",
       ....
       ....
     ]
نظرات مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت دوم
ممنون از پاسختون.
پراپرتی‌ها مقادیر درستی دارند و بعد از نگاشت مقادیر Null میگیرند.
این هم کد‌ها :
public class Kala {
        [Key]
        public int Kala_id { get; set; }

        [DisplayName("نام کالا")]
        public string Name { get; set; }

        [DisplayName("قیمت خرید")]
        public double Fee_Kharid { get; set; }
     
        public virtual Brand Brand { get; set; }
     }

  public class Brand
    {
        [Key]
        public int Brand_id { get; set; }
        public string Brand_Name { get; set; }
        public virtual ICollection<Kala> Kalas { get; set; }
    }

 public class KalaViewModel 
    {
        public int Kala_Id { get; set; }
        public string  Name { get; set; }
        public double Fee_Kharid { get; set; }
        public string Brand_Name { get; set; }
    }

     //Controller
     [HttpGet]
        public ActionResult Index()
        {
            var kala = _Kala_Service.GetAllKalas();
            var brand = _Brand_Service.GetAllBrands();

            var kalaviewmodel = EntityMapper.Map<List<KalaViewModel>>(kala, brand);
            return View(kalaviewmodel);
        }

 protected override void Configure()
        {
            Mapper.CreateMap<Kala, KalaViewModel>();

            Mapper.CreateMap<Brand, KalaViewModel>()
                      .ForMember(des => des.Kala_Id, op => op.Ignore())
                      .ForMember(des => des.Name, op => op.Ignore())
                      .ForMember(x => x.Fee_Kharid, opt => opt.Ignore());

        }
سپاس
نظرات مطالب
نحوه استفاده از ViewModel در ASP.NET MVC
لطف میکنید نحوه ارتباط viewmodel  رو زمانی که بین model  و view  قرار میگیره توضیح بدین .
اینکه در آخر ViewModel  رو چطور به Model  مرتبط میکنیم ؟
آیا امکان این که هم Model  و هم    ViewModel اعتبار سنجی کنیم کار درستیه ؟
بیشتر قسمت ربط ViewModel  به Model  برام اهمیت داره که متوجهش نمیشم .
با تشکر
مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
تا قسمت قبل، روشی را که برای کار با EF-Core درنظر گرفتیم، روش متداول کار با آن، در برنامه‌های ASP.NET Core Web API بود؛ یعنی این روش با برنامه‌های مبتنی بر Blazor WASM که از دو قسمت مجزای Web API سمت سرور و Web Assembly سمت کلاینت تشکیل شده‌اند، به خوبی جواب می‌دهد؛ اما ... با Blazor Server یکپارچه که تمام قسمت‌های مدیریتی آن سمت سرور رخ می‌دهند، خیر! در این مطلب، دلایل این موضوع را به همراه ارائه راه‌حلی، بررسی خواهیم کرد.


طول عمر سرویس‌ها، در برنامه‌های Blazor Server متفاوت هستند

هنگامیکه با یک ASP.NET Core Web API متداول کار می‌کنیم، درخواست‌های HTTP رسیده، از میان‌افزارهای موجود رد شده و پردازش می‌شوند. اما هنگامیکه با Blazor Server کار می‌کنیم، به علت وجود یک اتصال دائم SignalR که عموما از نوع Web socket است، دیگر درخواست HTTP وجود ندارد. تمام رفت و برگشت‌های برنامه به سرور و پاسخ‌های دریافتی، از طریق Web socket منتقل می‌شوند و نه درخواست‌ها و پاسخ‌های متداول HTTP.
این روش پردازشی، اولین تاثیری را که بر روی رفتار یک برنامه می‌گذارد، تغییر طول عمر سرویس‌های آن است. برای مثال در برنامه‌های Web API، طول عمر درخواست‌ها، از نوع Scoped هستند و با شروع پردازش یک درخواست، سرویس‌های مورد نیاز وهله سازی شده و در پایان درخواست، رها می‌شوند.
این مساله در حین کار با EF-Core نیز بسیار مهم است؛ از این جهت که در برنامه‌های Web API نیز EF-Core و DbContext آن، به صورت سرویس‌هایی با طول عمر Scoped تعریف می‌شوند. برای مثال زمانیکه یک چنین تعریفی را در برنامه داریم:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
امضای واقعی متد AddDbContext مورد استفاده‌ی فوق، به صورت زیر است:
public static IServiceCollection AddDbContext<TContext>(
    [NotNullAttribute] this IServiceCollection serviceCollection, 
    [CanBeNullAttribute] Action<DbContextOptionsBuilder> optionsAction = null, 
    ServiceLifetime contextLifetime = ServiceLifetime.Scoped, 
    ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext;
همانطور که مشاهده می‌کنید، طول عمرهای پیش‌فرض تعریف شده‌ی در اینجا، از نوع Scoped هستند. یعنی زمانیکه سرویس‌های ApplicationDbContext را از طریق سیستم تزریق وابستگی‌های برنامه دریافت می‌کنیم، در ابتدای یک درخواست Web API، به صورت خودکار وهله سازی شده و در پایان درخواست رها می‌شوند. به این ترتیب به ازای هر درخواست رسیده، وهله‌ی متفاوتی از DbContex را دریافت می‌کنیم که با وهله‌ی استفاده شده‌ی در درخواست قبلی، یکی نیست.
اما زمانیکه مانند یک برنامه‌ی مبتنی بر Blazor Server، دیگر HTTP Requests متداولی را نداریم، چطور؟ در این حالت زمانیکه یک اتصال SignalR برقرار شد، وهله‌ای از DbContext که در اختیار برنامه‌ی Blazor Server قرار می‌گیرد، تا زمانیکه کاربر این اتصال را به نحوی قطع نکرده (مانند بستن کامل مرورگر و یا ریفرش صفحه)، ثابت باقی خواهد ماند. یعنی به ازای هر اتصال SignalR، طول عمر ServiceLifetime.Scoped پیش‌فرض تعریف شده، همانند یک وهله‌ی با طول عمر Singleton عمل می‌کند. در این حالت تمام صفحات و کامپوننت‌های یک برنامه‌ی Blazor Server، از یک تک وهله‌ی مشخص DbContext که در ابتدای کار دریافت کرده‌اند، کار می‌کنند و از آنجائیکه DbContext به صورت thread-safe کار نمی‌کند، این تک وهله مشکلات زیادی را ایجاد خواهد کرد که یک نمونه از آن‌را در عمل، در پایان قسمت قبل مشاهده کردید:
«اگر برنامه را اجرا کرده و سعی در حذف یک ردیف کنیم، به خطای زیر می‌رسیم و یا حتی اگر کاربر شروع کند به کلیک کردن سریع در قسمت‌های مختلف برنامه، باز هم این خطا مشاهده می‌شود:
 An exception occurred while iterating over the results of a query for context type 'BlazorServer.DataAccess.ApplicationDbContext'.
System.InvalidOperationException: A second operation was started on this context before a previous operation completed.
This is usually caused by different threads concurrently using the same instance of DbContext.
For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
عنوان می‌کند که متد OnConfirmDeleteRoomClicked، بر روی ترد دیگری نسبت به ترد اولیه‌ای که DbContext بر روی آن ایجاد شده، در حال اجرا است و چون DbContext برای یک چنین سناریوهایی، thread-safe نیست، اجازه‌ی استفاده‌ی از آن‌را نمی‌دهد.»
هر درخواست Web API نیز بر روی یک ترد جداگانه اجرا می‌شود؛ اما چون ابتدا و انتهای درخواست‌ها مشخص است، طول عمر Scoped، در ابتدای درخواست شروع شده و در پایان آن رها سازی می‌شود. به همین جهت استثنائی را که در اینجا مشاهده می‌کنید، در برنامه‌های Web API شاید هیچگاه مشاهده نشود.


معرفی DbContextFactory در EF Core 5x

همواره باید طول عمر DbContext را تا جای ممکن، کوتاه نگه داشت. مشکل فعلی ما، Singleton رفتار کردن DbContext‌ها (داشتن طول عمر طولانی) در برنامه‌های Blazor Server هستند. یک چنین رفتاری را شاید در برنامه‌های دسکتاپ هم پیشتر مشاهده کرده باشید. برای مثال در برنامه‌های دسکتاپ WPF، تا زمانیکه یک فرم باز است، Context ایجاد شده‌ی در آن هم برقرار است و Dispose نمی‌شود. در یک چنین حالت‌هایی، عموما Context را در زمان نیاز، ایجاد کرده و پس از پایان آن کار کوتاه، Context را رها می‌کنند. به همین جهت نیاز به DbContext Factory ای وجود دارد که بتواند یک چنین پیاده سازی‌هایی را میسر کند و خوشبختانه از زمان EF Core 5x، یک چنین امکانی خصوصا برای برنامه‌های Blazor Server تحت عنوان DbContextFactory ارائه شده‌است که به عنوان راه حل استاندارد دسترسی به DbContext در اینگونه برنامه‌ها مورد استفاده قرار می‌گیرد.
برای کار با DbContextFactory، اینبار در فایل BlazorServer.App\Startup.cs، بجای استفاده از services.AddDbContext، از متد AddDbContextFactory استفاده می‌شود:
public void ConfigureServices(IServiceCollection services)
{
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    //services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
    services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
سپس باید دقت داشت که روش استفاده‌ی از آن، نسبت به کار مستقیم با ApplicationDbContext، کاملا متفاوت است. هدف از DbContextFactory، ساخت دستی Context در زمان نیاز و سپس Dispose صریح آن است. بنابراین طول عمر Context دریافت شده‌ی توسط آن باید توسط برنامه نویس مدیریت شود و به صورت خودکار توسط IoC Container برنامه مدیریت نخواهد شد. در این حالت دو روش استفاده‌ی از آن در کامپوننت‌های برنامه‌های Blazor Server، پیشنهاد می‌شود.


روش اول کار با DbContextFactory در کامپوننت‌های Blazor Server : وهله سازی از نو، به ازای هر متد

در این روش پس از ثبت AddDbContextFactory در فایل Startup برنامه مانند مثال فوق، ابتدا سرویس IDbContextFactory که به ApplicationDbContext اشاره می‌کند به ابتدای کامپوننت تزریق می‌شود:
@inject IDbContextFactory<ApplicationDbContext> DbFactory
سپس در هر جائی که نیاز به وهله‌ای از ApplicationDbContext است، آن‌را به صورت دستی وهله سازی کرده و همانجا هم Dispose می‌کنیم:
private async Task DeleteImageAsync()
{
    using var context = DbFactory.CreateDbContext();

    var image = await context.HotelRoomImages.FindAsync(1);

   // ...
}
در اینجا یکی متدهای یک کامپوننت فرضی را مشاهده می‌کند که از DbFactory تزریق شده استفاده کرد و سپس با استفاده از متد ()CreateDbContext، وهله‌ی جدیدی از ApplicationDbContext را ایجاد می‌کند. همچنین در همان سطر، وجود عبارت using نیز مشاهده می‌شود. یعنی در پایان کار این متد، context ایجاد شده حتما Dispose شده و طول عمر کوتاهی خواهد داشت.


روش دوم کار با DbContextFactory در کامپوننت‌های Blazor Server : یکبار وهله سازی Context به ازای هر کامپوننت

در این روش می‌توان طول عمر Context را معادل طول عمر کامپوننت تعریف کرد که مزیت استفاده‌ی از Change tracking موجود در EF-Core را به همراه خواهد داشت. در این حالت کامپوننت‌های Blazor Server، شبیه به فرم‌های برنامه‌های دسکتاپ عمل می‌کنند:
@implements IDisposable
@inject IDbContextFactory<ApplicationDbContext> DbFactory


@code
{
   private ApplicationDbContext Context;

   protected override async Task OnInitializedAsync()
   {
       Context = DbFactory.CreateDbContext();
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       var image = await Context.HotelRoomImages.FindAsync(1);
       // ...
   }

   public void Dispose()
   {
     Context.Dispose();
   }
}
- در اینجا همانند روش اول، کار با تزریق IDbContextFactory شروع می‌شود
-  اما بجای اینکه به ازای هر متد، کار فراخوانی DbFactory.CreateDbContext صورت گیرد، یکبار در آغاز کار کامپوننت و در روال رویدادگردان OnInitializedAsync، کار وهله سازی Context کامپوننت انجام شده و از این تک Context در تمام متدهای کامپوننت استفاده خواهد شد.
- در این حالت کار Dispose خودکار این Context به متد Dispose نهایی کل کامپوننت واگذار شده‌است. برای اینکه این متد فراخوانی شود، نیاز است در ابتدای تعاریف کامپوننت، از دایرکتیو implements IDisposable@ استفاده کرد.


سؤال: اگر سرویسی از ApplicationDbContext تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند، چکار باید کرد؟

برای نمونه سرویس‌های از پیش تعریف شده‌ی ASP.NET Core Identity، در سازنده‌ی خود از ApplicationDbContext استفاده می‌کنند و نه از IDbContextFactory. در این حالت برای تامین ApplicationDbContext‌های تزریق شده، فقط کافی است از روش زیر استفاده کنیم:
services.AddScoped<ApplicationDbContext>(serviceProvider =>
     serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
در این حالت به ازای هر Scope تعریف شده‌ی در برنامه، جهت دسترسی به ApplicationDbContext از طریق سیستم تزریق وابستگی‌ها، کار فراخوانی DbFactory.CreateDbContext به صورت خودکار انجام خواهد شد.


سؤال: روش پیاده سازی سرویس‌های یک برنامه Blazor Server به چه صورتی باید تغییر کند؟

تا اینجا روش‌هایی که برای استفاده از IDbContextFactory معرفی شدند (که روش‌های رسمی و توصیه شده‌ی اینکار نیز هستند)، فرض را بر این گذاشته‌اند که ما قرار است تمام منطق تجاری کار با بانک اطلاعاتی را داخل همان متدهای کامپوننت‌ها انجام دهیم (این روش برنامه نویسی، بسیار مورد علاقه‌ی مایکروسافت است و در تمام مثال‌های رسمی آن به صورت ضمنی توصیه می‌شود!). اما اگر همانند مثالی که تاکنون در این سری بررسی کردیم، نخواهیم اینکار را انجام دهیم و علاقمند باشیم تا این منطق تجاری را به سرویس‌های مجزایی، با مسئولیت‌های مشخصی انتقال دهیم، روش استفاده‌ی از IDbContextFactory چگونه خواهد بود؟
در این حالت از ترکیب روش دوم مطرح شده‌ی استفاده از IDbContextFactory که به همراه مزیت دسترسی کامل به Change Tracking توکار EF-Core و پیاده سازی الگوی واحد کار است و وهله سازی خودکار ApplicationDbContext که معرفی شد، استفاده خواهیم کرد؛ به این صورت:
الف) تمام سرویس‌های EF-Core یک برنامه‌ی Blazor Server باید اینترفیس IDisposable را پیاده سازی کنند.
این مورد برای سرویس‌های پروژه‌های Web API، ضروری نیست؛ چون طول عمر Context آن‌ها توسط خود IoC Container مدیریت می‌شود؛ اما در برنامه‌های Blazor Server، مطابق توضیحاتی که ارائه شد، خودمان باید این طول عمر را مدیریت کنیم.
بنابراین به پروژه‌ی سرویس‌های برنامه مراجعه کرده و هر سرویسی که ApplicationDbContext تزریق شده‌ای را در سازنده‌ی خود می‌پذیرد، یافته و تعریف اینترفیس آن‌را به صورت زیر تغییر می‌دهیم:
public interface IHotelRoomService : IDisposable
{
   // ...
}

public interface IHotelRoomImageService : IDisposable
{
   // ...
}
سپس باید اینترفیس‌های IDisposable را پیاده سازی کرد که روش مورد پذیرش code analyzer‌ها در این زمینه، رعایت الگوی زیر، دقیقا به همین شکل است و باید از دو متد تشکیل شود:
    public class HotelRoomService : IHotelRoomService
    {
        private bool _isDisposed;

        // ...

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                try
                {
                    if (disposing)
                    {
                        _dbContext.Dispose();
                    }
                }
                finally
                {
                    _isDisposed = true;
                }
            }
        }
    }
این الگو را به همین شکل برای سرویس HotelRoomImageService نیز پیاده سازی می‌کنیم.


ب) Dispose دستی تمام سرویس‌ها، در کامپوننت‌های مرتبط
در ادامه تمام کامپوننت‌هایی را که از سرویس‌های فوق استفاده می‌کنند یافته و ابتدا دایرکتیو implements IDisposable@ را به ابتدای آن‌ها اضافه می‌کنیم. سپس متد Dispose آن‌ها را جهت فراخوانی متد Dispose سرویس‌های فوق، تکمیل خواهیم کرد:
بنابراین ابتدا به فایل BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomUpsert.razor مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomImageService.Dispose();
        HotelRoomService.Dispose();
    }
}
و همچنین به کامپوننت BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر جهت Dispose دستی سرویس‌ها، تکمیل می‌کنیم:
@page "/hotel-room"

@implements IDisposable
// ...


@code
{
    // ...

    public void Dispose()
    {
        HotelRoomService.Dispose();
    }
}


مشکل! اینبار خطای dispose شدن context را دریافت می‌کنیم!

System.ObjectDisposedException: Cannot access a disposed context instance.
A common cause of this error is disposing a context instance that was resolved from dependency injection and then
later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose'
on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
هم برنامه‌های Blazor WASM و هم برنامه‌های Blazor Server از مفهوم طول عمرهای تنظیم شده‌ی سرویس‌ها پشتیبانی نمی‌کنند! در هر دوی این‌ها اگر سرویسی را با طول عمر Scoped تنظیم کردیم، رفتار آن همانند سرویس‌های Singleton خواهد بود. تنها زمانی رفتارهای Scoped و یا Transient پشتیبانی می‌شوند که درخواست HTTP ای رخ داده باشد که این مورد خارج است از طول عمر یک برنامه‌ی Blazor WASM و همچنین اتصال SignalR برنامه‌های Blazor Server. فقط قسمت‌هایی از برنامه‌ی Blazor Server که با مدل قبلی Razor pages طراحی شده‌اند، چون سبب شروع یک درخواست HTTP معمولی می‌شوند، همانند برنامه‌های متداول ASP.NET Core رفتار می‌کنند و در این حالت طول عمرهای غیر Singleton مفهوم پیدا می‌کنند.

مشکلی که در اینجا رخ داده این است که سرویس‌هایی را داریم با طول عمر به ظاهر Scoped که یکی از وابستگی‌های آن‌ها را به صورت دستی Dispose کرده‌ایم. چون طول عمر Scoped در اینجا وجود ندارد و طول عمرها در اصل Singleton هستند، هربار که سرویس مدنظر مجددا درخواست شود، همان وهله‌ی ابتدایی که اکنون یکی از وابستگی‌های آن Dispose شده، در اختیار برنامه قرار می‌گیرد.
پس از این تغییرات، اولین باری که برنامه را اجرا می‌کنیم، لیست اتاق‌ها به خوبی نمایش داده می‌شوند و مشکلی نیست. بعد در همین حال و در همین صفحه، اگر بر روی دکمه‌ی افزودن یک اتاق جدید کلیک کنیم، اتفاقی که رخ می‌دهد، فراخوانی متد Dispose کامپوننت لیست اتاق‌ها است (بر روی آن یک break-point قرار دهید). بنابراین متد Dispose یک کامپوننت، با هدایت به یک مسیر دیگر، به صورت خودکار فراخوانی می‌شود. در این حالت Context برنامه Dispose شده و در کامپوننت ثبت یک اتاق جدید دیگر، در دسترس نخواهد بود؛ چون IHotelRoomService مورد استفاده مجددا وهله سازی نمی‌شود و از همان وهله‌ای که بار اول ایجاد شده، استفاده خواهد شد.
 
بنابراین سؤال اینجا است که چگونه می‌توان سیستم تزریق وابستگی‌ها را وادار کرد تا تمام سرویس‌های تزریق شده‌ی به سازنده‌ها‌ی سرویس‌های HotelRoomService و  HotelRoomImageService را مجددا وهله سازی کند و سعی نکند از همان وهله‌های قبلی استفاده کند؟

پاسخ: یک روش این است که IHotelRoomImageService را خودمان به ازای هر کامپوننت به صورت دستی در روال رویدادگردان OnInitializedAsync وهله سازی کرده و DbFactory.CreateDbContext جدیدی را مستقیما به سازنده‌ی آن ارسال کنیم. در این حالت مطمئن خواهیم شد که این وهله، جای دیگری به اشتراک گذاشته نمی‌شود:
@code
{
   private IHotelRoomImageService HotelRoomImageService;

   protected override async Task OnInitializedAsync()
   {
       HotelRoomImageService =  new HotelRoomImageService(DbFactory.CreateDbContext(), mapper);
       await base.OnInitializedAsync();
   }

   private async Task DeleteImageAsync()
   {
       await HotelRoomImageService.DeleteAsync(1);
       // ...
   }

   public void Dispose()
   {
     HotelRoomImageService.Dispose();
   }
}
هرچند این روش کار می‌کند، اما در زمان استفاده از IoC Container‌ها قرار نیست کار انجام new‌ها را خودمان به صورت دستی انجام دهیم و بهتر است مدیریت این مساله به آن‌ها واگذار شود.


وادار کردن Blazor Server به وهله سازی مجدد سرویس‌های کامپوننت‌ها

بنابراین مشکل ما Singleton رفتار کردن سرویس‌ها، در برنامه‌های Blazor است. برای مثال در برنامه‌های Blazor Server، تا زمانیکه اتصال SignalR برنامه برقرار است (مرورگر بسته نشده، برگه‌ی جاری بسته نشده و یا کاربر صفحه را ریفرش نکرده)، هیچ سرویسی دوباره وهله سازی نمی‌شود.
برای رفع این مشکل، امکان Scoped رفتار کردن سرویس‌های یک کامپوننت نیز در نظر گرفته شده‌اند. برای نمونه کدهای کامپوننت HotelRoomList.razor را به صورت زیر تغییر می‌دهیم:
@page "/hotel-room"

@*@implements IDisposable*@
@*@inject IHotelRoomService HotelRoomService*@
@inherits OwningComponentBase<IHotelRoomService>
با استفاده از دایرکتیو جدید inherits OwningComponentBase@ می‌توان میدان دید یک سرویس را به طول عمر کامپوننت جاری محدود کرد. هربار که این کامپوننت نمایش داده می‌شود، وهله سازی شده و هربار که به کامپوننت دیگری هدایت می‌شویم، به صورت خودکار سرویس مورد استفاده را Dispose می‌کند. بنابراین در اینجا دیگر نیازی به ذکر دایرکتیو implements IDisposable@ نیست.

چند نکته:
- فقط یکبار به ازای هر کامپوننت می‌توان از دایرکتیو inherits استفاده کرد.
- زمانیکه طول عمر سرویسی را توسط OwningComponentBase مدیریت می‌کنیم، در حقیقت یک کلاس پایه را برای آن کامپوننت درنظر گرفته‌ایم که به همراه یک خاصیت عمومی ویژه، به نام Service و از نوع سرویس مدنظر ما است. در این حالت یا می‌توان از خاصیت Service به صورت مستقیم استفاده کرد و یا می‌توان به صورت زیر، همان کدهای قبلی را داشت و هربار که نیازی به HotelRoomService بود، آن‌را به خاصیت عمومی Service هدایت کرد:
@code
{
   private IHotelRoomService HotelRoomService => Service;
- اگر نیاز به بیش از یک سرویس وجود داشت، چون نمی‌توان بیش از یک inherits را تعریف کرد، می‌توان از نمونه‌ی غیرجنریک OwningComponentBase استفاده کرد:
@page "/preferences"
@using Microsoft.Extensions.DependencyInjection
@inherits OwningComponentBase


@code {
    private IHotelRoomService HotelRoomService { get; set; }
    private IHotelRoomImageService HotelRoomImageService { get; set; }

    protected override void OnInitialized()
    {
        HotelRoomService = ScopedServices.GetRequiredService<IHotelRoomService>();
        HotelRoomImageService = ScopedServices.GetRequiredService<IHotelRoomImageService>();
    }
}
در این حالت کلاس پایه‌ی OwningComponentBase، به همراه خاصیت جدید ScopedServices است که با فراخوانی متد GetRequiredService در روال رویدادگردان OnInitialized بر روی آن، سبب وهله سازی Scoped سرویس مدنظر خواهد شد. نمونه‌ی جنریک آن، تمام این موارد را در پشت صحنه انجام می‌دهد و کار کردن با آن ساده‌تر و خلاصه‌تر است.


خلاصه‌ی بحث جاری در مورد روش مدیریت DbContext برنامه‌های Blazor Server:

- بجای services.AddDbContext متداول، باید از AddDbContextFactory استفاده کرد:
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<ApplicationDbContext>(serviceProvider =>
        serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
- تمام سرویس‌هایی که از ApplicationDbContext استفاده می‌کنند، باید به همراه پیاده سازی Dispose آن نیز باشند؛ چون Scope یک سرویس، معادل طول عمر اتصال SignalR برنامه است و مدام وهله سازی نمی‌شود. در این حالت باید وهله سازی و Dispose آن‌را دستی مدیریت کرد.
- کامپوننت‌های برنامه، سرویس‌هایی را که باید Scoped عمل کنند، دیگر نباید از طریق تزریق مستقیم آن‌ها دریافت کنند؛ چون در این حالت همواره به همان وهله‌ای که در ابتدای کار ایجاد شده، می‌رسیم:
@inject IHotelRoomService HotelRoomService
این دریافت باید با استفاده از کلاس پایه OwningComponentBase صورت گیرد:
@inherits OwningComponentBase<IHotelRoomService>
تا عملیات فراخوانی خودکار ScopedServices.GetRequiredService (دریافت وهله‌ی جدید Scoped) و همچنین Dispose خودکار آن‌ها را به ازای هر کامپوننت مجزا، مدیریت کند.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-19.zip
نظرات مطالب
خواندن اطلاعات از سرور و نمایش آن توسط Angular در ASP.NET MVC
با تشکر 
من از این روش در یک view استفاده کردم (در اینجا هدف نمایش لیست کاربران بود) ولی مشکل من این هست که وقتی این view برای کاربر نمایش داده میشه چون انگولار اون رو کش میکنه با افزودن کاربر جدید این لیست تا درخواست مجدد از سرور بروز نمیشه.
میخواستم بدونم آیا با واکشی اطلاعات توسط http$ مشکلم حل میشه؟
یا میشه یک view در انگولار کش نشود و به هر با route به اون view مورد نظر از سرور فراخوانی بشه
مطالب
نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
این مطلب در ادامه بحث «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشد. بنابراین تعاریف مدل و کنترلر آن، به همراه توضیحات ذکر شده در آن، در ادامه مورد استفاده قرار خواهند گرفت.


اصول نمایش Popover در Twitter bootstrap

PopOverها نیز یکی دیگر از کامپوننت‌های جاوا اسکریپتی مجموعه بوت استرپ هستند. بسیار شبیه به Tooltip بوده، اما ماندگارتر هستند. PopOverها با کلیک بر روی یک عنصر باز شده و تنها با کلیک مجدد بر روی آن المان، بسته می‌شوند (البته این موارد نیز قابل تنظیم هستند).
<a rel="popover" 
               data-content="محتوایی برای نمایش" 
               data-original-title="عنوان" href="#">اطلاعات</a>

    <script type="text/javascript">
        $(document).ready(function () {
            $("[rel='popover']").popover({ placement: 'left' })
                            .click(function (e) { e.preventDefault(); });
        });
   </script>
نحوه استفاده از آن را در مثال فوق مشاهده می‌کنید. در اینجا یک لینک با rel=popover تعریف شده است. از این rel، در یافتن کلیه المان‌هایی اینگونه، توسط jQuery استفاده خواهیم کرد. سپس مقدار ویژگی data-content، محتوای اطلاعاتی را که باید نمایش داده شود، مشخص می‌کند. همچنین برای مشخص ساختن عنوان آن می‌توان از ویژگی data-original-title استفاده کرد. نهایتا نیاز است افزونه popover بر روی المان‌هایی با rel=popover فراخوانی گردد. در روال رخدادگردان click آن، با استفاده از e.preventDefault، سبب خواهیم شد تا با کلیک بر روی لینک تعریف شده، صفحه مجددا بازیابی نشده و مکان اسکرول عمودی صفحه، تغییر نکند.


تبدیل خطاهای اعتبارسنجی ASP.NET MVC به PopOver

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

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


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

@model Mvc4TwitterBootStrapTest.Models.User
@{
    ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" })

    <fieldset class="form-horizontal">
        <legend>تعریف کاربر جدید</legend>
        <div class="control-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.Name)
                @*@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="control-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label" })
            <div class="controls">
                @Html.EditorFor(model => model.LastName)
                @*@Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })*@
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                ارسال</button>
        </div>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $.validator.setDefaults({
            showErrors: function (errorMap, errorList) {
                this.defaultShowErrors();
                //اگر المانی معتبر است نیاز به نمایش پاپ اور ندارد
                $("." + this.settings.validClass).popover("destroy");
                //افزودن پاپ اورها
                for (var i = 0; i < errorList.length; i++) {
                    var error = errorList[i];
                    $(error.element).popover({ placement: 'left' })
                                    .attr("data-original-title", "خطای اعتبارسنجی")
                                    .attr("data-content", error.message);
                }
            },
            // همانند قبل برای رنگی کردن کل ردیف در صورت عدم اعتبار سنجی و برعکس
            highlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).addClass(errorClass).removeClass(validClass);
                } else {
                    $(element).addClass(errorClass).removeClass(validClass);
                    $(element).closest('.control-group').removeClass('success').addClass('error');
                }
                $(element).trigger('highlited');
            },
            unhighlight: function (element, errorClass, validClass) {
                if (element.type === 'radio') {
                    this.findByName(element.name).removeClass(errorClass).addClass(validClass);
                } else {
                    $(element).removeClass(errorClass).addClass(validClass);
                    $(element).closest('.control-group').removeClass('error').addClass('success');
                }
                $(element).trigger('unhighlited');
            }
        });
        //برای حالت پست بک از سرور عمل می‌کند
        $(function () {
            $('form').each(function () {
                $(this).find('div.control-group').each(function () {
                    if ($(this).find('span.field-validation-error').length > 0) {
                        $(this).addClass('error');
                    }
                });
            });
        });
   </script>
}
کدهای مدل و کنترلر، همانند مطلب «اعمال کلاس‌های ویژه اعتبارسنجی Twitter bootstrap به فرم‌های ASP.NET MVC» می‌باشند و از تکرار مجدد آن‌ها در اینجا صرفنظر گردید.

توضیحات
- با توجه به اینکه دیگر نمی‌خواهیم خطاها به صورت برچسب در مقابل کنترل‌ها نمایش داده شوند، کلیه Html.ValidationMessageFor به صورت کامنت درآورده شده‌اند.
- تغییر دوم مطلب جاری، اضافه شدن متد showErrors به تنظیمات پیش فرض jQuery Validator است. در این متد، اگر المانی معتبر بود، Popover آن حذف می‌شود یا در سایر حالات، المان‌هایی که نیاز به اعتبارسنجی سمت کلاینت دارند، یافت شده و سپس ویژگی data-content با مقداری معادل خطای اعتبارسنجی متناظر، به این المان افزوده و سپس متد popover بوت استرپ بر روی آن فراخوانی می‌گردد.
به عبارتی زمانیکه یک input box در ASP.NET MVC به همراه مقادیر مرتبط با اعتبارسنجی آن رندر می‌شود، چنین شکلی را خواهد داشت:
<input class="text-box single-line" data-val="true" data-val-required="لطفا نام را تکمیل کنید"
 id="Name" name="Name" type="text" value="" />
اما در اینجا به صورت پویا، data-original-title و data-content نیز به آن افزوده می‌گردند:
<input class="text-box single-line input-validation-error" data-val="true" data-val-required="لطفا نام را تکمیل کنید"
 id="Name" name="Name" type="text" value="" 
data-original-title="خطای اعتبارسنجی" title="" data-content="لطفا نام را تکمیل کنید">
این مقادیر توسط افزونه popover بوت استرپ شناسایی شده و مورد استفاده قرار می‌گیرد.
البته این موارد را در صورت نیاز به صورت دستی نیز می‌توان تعریف و اضافه کرد:
 @Html.TextBoxFor(x => x.Name, 
         new { data_content = "Name is required", 
               data_original_title = "Error", rel="popover" })
مطالب
آشنایی با Refactoring - قسمت 2

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

برای نمونه به مثال زیر دقت کنید:
using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.Before
{
public class Receipt
{
private IList<decimal> Discounts { get; set; }
private IList<decimal> ItemTotals { get; set; }

public decimal CalculateGrandTotal()
{
// Calculate SubTotal
decimal subTotal = 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;

// Calculate Discounts
if (Discounts.Count > 0)
{
foreach (decimal discount in Discounts)
subTotal -= discount;
}

// Calculate Tax
decimal tax = subTotal * 0.065m;
subTotal += tax;

return subTotal;
}
}
}

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

using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.After
{
public class Receipt
{
private IList<decimal> Discounts { get; set; }
private IList<decimal> ItemTotals { get; set; }

public decimal CalculateGrandTotal()
{
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);
return subTotal;
}

private decimal CalculateTax(decimal subTotal)
{
decimal tax = subTotal * 0.065m;
subTotal += tax;
return subTotal;
}

private decimal CalculateDiscounts(decimal subTotal)
{
if (Discounts.Count > 0)
{
foreach (decimal discount in Discounts)
subTotal -= discount;
}
return subTotal;
}

private decimal CalculateSubTotal()
{
decimal subTotal = 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;
return subTotal;
}
}
}

بهتر شد! عملکرد کد نهایی، تغییری نکرده اما کیفیت کد ما بهبود یافته است (همان مفهوم و معنای Refactoring). خوانایی کد افزایش یافته است. نیاز به کامنت نویسی به شدت کاهش پیدا کرده و از همه مهم‌تر، اعمال مختلف، در متدهای خاص آن‌ها قرار گرفته‌اند.
به همین جهت اگر حین کد نویسی، به یک متد طولانی برخوردید (این مورد بسیار شایع است)، در ابتدا حداقل کاری را که جهت بهبود کیفیت آن می‌توانید انجام دهید، «استخراج متدها» است.

ابزارهای کمکی جهت پیاده سازی روش «استخراج متدها»:

- ابزار Refactoring توکار ویژوال استودیو پس از انتخاب یک قطعه کد و سپس کلیک راست و انتخاب گزینه‌ی Refactor->Extract method، این عملیات را به خوبی می‌تواند مدیریت کند و در وقت شما صرفه جویی خواهد کرد.
- افزونه‌های ReSharper و همچنین CodeRush نیز چنین قابلیتی را ارائه می‌دهند؛ البته توانمندی‌های آن‌ها از ابزار توکار یاد شده بیشتر است. برای مثال اگر در میانه کد شما جایی return وجود داشته باشد، گزینه‌ی Extract method ویژوال استودیو کار نخواهد کرد. اما سایر ابزارهای یاده شده به خوبی از پس این موارد و سایر موارد پیشرفته‌تر بر می‌آیند.

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


و ... «لطفا» این نوع پیاده سازی‌ها را خارج از فایل code behind هر نوع برنامه‌ی winform/wpf/asp.net و غیره قرار دهید. تا حد امکان سعی کنید این مکان‌ها، استفاده کننده‌ی «نهایی» منطق‌های پیاده سازی شده توسط کلاس‌های دیگر باشند؛ نه اینکه خودشان محل اصلی قرارگیری و ابتدای تعریف منطق‌های مورد نیاز قسمت‌های مختلف همان فرم مورد نظر باشند. «لطفا» یک فرم درست نکنید با 3000 سطر کد که در قسمت code behind آن قرار گرفته‌اند. code behind را محل «نهایی» ارائه کار قرار دهید؛ نه نقطه‌ی آغاز تعریف منطق‌های پیاده سازی کار. این برنامه نویسی چندلایه که از آن صحبت می‌شود، فقط مرتبط با کار با بانک‌های اطلاعاتی نیست. در همین مثال، کدهای فرم برنامه، باید نقطه‌ی نهایی نمایش عملیات محاسبه مالیات باشند؛ نه اینکه همانجا دوستانه یک قسمت مالیات حساب شود، یک قسمت تخفیف، یک قسمت جمع بزند، همانجا هم نمایش بدهد! بعد از یک هفته می‌بینید که code behind فرم در حال انفجار است! شده 3000 سطر! بعد هم سؤال می‌پرسید که چرا اینقدر میل به «بازنویسی» سیستم این اطراف زیاد است! برنامه نویس حاضر است کل کار را از صفر بنویسد، بجای اینکه با این شاهکار بخواهد سرو کله بزند! هر چند یکی از روش‌های برخورد با این نوع کدها جهت کاهش هراس نگهداری آن‌ها، شروع به Refactoring است.

نظرات مطالب
سفارشی کردن ASP.NET Identity در MVC 5
با سلام و احترام 
من دستور زیر رو تو asp.net mvc Identity sample 2 تو اکشن Login  اضافه کردم 
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));         
    var user = UserManager.FindById(User.Identity.GetUserId());
اما user و null میده !
اکشن login
 public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            // This doen't count login failures towards lockout only two factor authentication
            // To enable password failures to trigger lockout, change to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            switch (result)
            {

                case SignInStatus.Success:
                    {
                        UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
                        var user = UserManager.FindById(User.Identity.GetUserId());
                        return RedirectToLocal(returnUrl);
                    }
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }