مطالب
آموزش 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 لود خواهد شد.

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


نظرات مطالب
EF Code First #12
متشکرم.
منظور من از داشتن دیتایس به ازای هر موجودیت بد بیان شد.
یک DbContext داریم حاوی DbSet ها.
در یک برنامه‌ی ویندوزی کاربر می‌تواند دیتابیس‌های مختلف با نام‌های مختلف با یک طراحی داشته باشد.
در واقع به دلایلی مثل محدودیت SQL Server Express در ذخیره سازی (10 گیگ) طراحی به گونه ای انجام شده که برنامه بتواند به ازای هر پروژه یک دیتابیس داشته باشد.
برنامه به یک دیتابیس وصل است و زمانی که کاربر قصد ایجاد یک پروژه‌ی جدید دارد باید یک دیتابیس جدید ایجاد شود و Connection String عوض شود. و از این پس DbContext باید به دیتابیس جدید متصل شود.
public static void ChangeDatabase(string name)
{
    var sqlConnectionStringBuilder =
        new SqlConnectionStringBuilder(ConfigHelper.ActiveConnection);
    sqlConnectionStringBuilder["Database"] = name
    ConfigHelper.ActiveConnection = sqlConnectionStringBuilder.ToString();
    Database.DefaultConnectionFactory =
        new System.Data.Entity.Infrastructure.SqlConnectionFactory(ConfigHelper.ActiveConnectionString());

    Database.SetInitializer(
        new MigrateDatabaseToLatestVersion<TestContext, MigrationConfiguration>());
    using (var context = new TestContext())
    {
        context.Database.Initialize(true);
    }
}




مطالب
سرورهای متصل شده‌ی SQL Server و مبحث تراکنش‌ها

یکی از قابلیت‌های جالب SQL Server در یک شبکه محلی امکان link و اتصال آن‌ها به یکدیگر است. به این صورت امکان کوئری گرفتن (و یا اعمال متداول SQL ایی) از دو یا چند سرور مختلف با دستورات T-SQL میسر می‌شود؛ به نحوی که حس یکپارچگی دیتابیس‌های این سرورها را حین کوئری نوشتن خواهیم داشت.
برای مثال فرض کنید دو سرور SQL1 و SQL2 را در شبکه داریم. می‌خواهیم در سرور SQL1 اتصالی را به سرور SQL2 ایجاد کنیم.

USE master

EXEC sp_addlinkedserver
'SQL2',
N'SQL Server'

sp_addlinkedsrvlogin @useself='false ', @rmtsrvname = 'SQL2',
@rmtuser = 'sa',
@rmtpassword = 'pass#'

دستورات T-SQL فوق کار ثبت یک liked server جدید و اعمال مشخصات کاربری که توسط آن قرار است به سرور SQL2 دسترسی داشت، انجام می‌دهند.
اکنون جهت بررسی این اتصال در سرور SQL1 کوئری زیر را اجرا می‌کنیم:

select * from sql2.faxManager.dbo.tblErja

که نحوه‌ی فراخوانی جدول مورد نظر باید به صورت Server.DatabaseName.dbo.TableName در آن رعایت شود.
تا اینجا همه چیز خوب است. مشکل از زمانی شروع می‌شود که بخواهیم تراکنش‌ها را نیز دخالت دهیم و اصولی کار کنیم. برای مثال:

begin distributed tran
select * from sql2.faxManager.dbo.tblErja
commit tran

خطایی که در ویندوز سرور 2003 با آخرین به روز رسانی‌ها ظاهر می‌شود به صورت زیر است:

The operation could not be performed because OLE DB provider for linked server was unable to begin a distributed transaction.
OLE DB provider for linked server returned message "The partner transaction manager has disabled its support for remote/network transactions.".


به صورت پیش فرض این نوع تراکنش‌های توزیع شده غیرفعال هستند مگر اینکه فعال شوند و روش حل مشکل نیز به صورت زیر می‌باشد:
قبل از هر کاری به کنسول سرویس‌های ویندوز مراجعه کرده و از در حال اجرا بودن سرویس Distribute Transaction Coordinator اطمینان حاصل کنید.
سپس به قسمت زیر مراجعه نمائید:
Control Panel > Administrative Tools > Component Services


نود مربوط به Component Service را گشوده و سپس بر روی My Computer کلیک راست کرده و گزینه‌ی خواص را انتخاب کنید.
در صفحه‌ی بازه شده به برگه‌ی MSTDC مراجعه کرده و بر روی دکمه‌ی Security Configuration کلیک نمائید.
اکنون تنظیمات آن‌را مطابق شکل زیر تغییر دهید.


این تنظیم باید بر روی هر دو سرور SQL1 و SQL2 انجام شود.

پس از این تغییرات که شامل راه اندازی مجدد سرویس Distribute Transaction Coordinator نیز خواهد شد، مشکل خطای فوق برطرف شده و امکان استفاده از تراکنش‌ها در linked servers نیز میسر می‌شود.

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

Unable to start a nested transaction for OLE DB provider for linked server . A nested transaction was required because the XACT_ABORT option was set to OFF.
OLE DB provider for linked server returned message "Cannot start more transactions on this session.".


برای حل این مشکل یک سطر زیر را باید به ابتدای کوئری خود اضافه کرد که جزو الزامات تراکنش‌های توزیع شده است و به این صورت از rollback کامل تمامی دستورات موجود فراخوانی شده T-SQL در صورت بروز کوچکترین خطایی اطمینان حاصل می‌کند:
SET XACT_ABORT ON


برای مطالعه بیشتر:
MSDTC Troubleshooting

اشتراک‌ها
یک روز در شرکت خدمات انفورماتیک، قلب بانکداری الکترونیکی کشور

«... شرکت خدمات انفورماتیک سال ۷۲ به نام بهنمایان و بهسازان ثبت شد ولی همان سال تغییر نام داد تا حدود یک دهه بعد وارد بورس شود و البته سرمایه ثبت‌شده‌ای معادل ۲۸۰ میلیارد تومان در اختیار دارد. ایجاد و توسعه شبکه ماهواره‌ای کشور، اولین نظام‌های جامع بانکداری و نرم‌افزاری، نزدیک به یک دهه پیش شبکه انقلابی شتاب و چند سال بعد ساتنا را راه‌اندازی کرده‌است؛ چالشی به نام هدفمندی یارانه‌ها را دوام آورده و حالا نوبت سامانه تراکنش‌های پایانه‌های فروش است. ...»

یک روز در شرکت خدمات انفورماتیک، قلب بانکداری الکترونیکی کشور
مطالب
MongoDB #6
ساخت مجموعه در MongoDB

متد ()CreateCollection
دستور (db.createCollection(name, options در MongoDB برای ساخت مجموعه بکار برده می‌شود.

گرامر:
گرامر پایه دستور ()createCollection به شکل زیر است:
(db.createCollection(name, options
در این دستور، پارامتر name نام مجموعه‌ای است که باید ساخته شود. پارامتر Option یک سند است و برای تعیین پیکربندی مجموعه استفاده می‌شود.
پارامتر نوع توضیحات 
 name  رشته  نام مجموعه‌ای است که باید ساخته شود
 Option  سند  (اختیاری) تعیین  اختیارات برای اندازه حافظه و ایندکس گذاری
 پارامتر Option اختیاری است. در جدول زیر لیست اختیارتی را که می‌توانید استفاده کنید آمده است:
 فیلد  نوع توضیحات 
capped
Boolean
 (اختیاری) اگر مقدار آن true باشد یک مجموعه‌ی پوشیده (capped) در اختیار می‌گذارد. مجموعه‌ی پوشیده یک مجموعه با اندازه ثابت است که وقتی به حداکثر اندازه خود برسد، داده‌های جدید را بصورت اتوماتیک جایگزین قدیمی‌ترین داده‌ها می‌کند. اگر این پارامتر را true تنظیم کرده باشید، باید پارامتر size راهم مقداردهی کنید.
AutoIndexID Boolean
 (اختیاری) اگر true باشد، بصورت اتوماتیک روی فیلد _id ایندکس می‌سازد.  مقدار پیش فرض این پارامتر false است.
 size number (اختیاری) تعیین کننده‌ی حداکثر اندازه به بایت برای مجموعه پوشیده. اگر پارامتر capped برابر true باشد آنگاه نیاز دارید این پارامتر را نیز مقداردهی کنید.
 max number  (اختیاری) تعیین کننده حداکثر تعداد سندهای مجاز در یک مجموعه پوشیده

هنگام درج یک سند، MongoDB ابتدا فیلد capped و سپس فیلد max را بررسی می‌کند.


مثال:

گرامر پایه متد ()createCollection بدون اختیارات به شکل زیر است:

>use test
switched to db test
>db.createCollection("mycollection")
{ "ok" : 1 }
>

با استفاده از دستور show collection می‌توانید مجموعه ساخته شده را بررسی کنید:

>show collections
mycollection
system.indexes

مثال زیر گرامر متد ()createCollection، با اختیارات مهم‌تر را نمایش می‌دهد:

>db.createCollection("mycol", { capped : true, autoIndexID : true, size : 6142800, max : 10000 } )
{ "ok" : 1 }
>

در MongoDB، نیازی به ساخت مجموعه ندارید. وقتی یک سند را درج کنید، MongoDB بصورت اتوماتیک مجموعه را می‌سازد.

>db.tutorialspoint.insert({"name" : "tutorialspoint"}) 
>show collections 
mycol 
mycollection 
system.indexes 
tutorialspoint 
>

حذف مجموعه‌ها در MongoDB

متد ()drop

دستور ()db.collection.drop  برای حذف یک مجموعه از پایگاه داده استفاده می‌شود.

گرامر:

گرامر پایه دستور ()drop به شکل زیر است:

 db.COLLECTION_NAME.drop()

مثال:

ابتدا همه مجموعه‌های موجود در پایگاه داده mydb را بررسی کنید:

 >use mydb
switched to db mydb
>show collections
mycol
mycollection
system.indexes
tutorialspoint
>
اکنون مجموعه با نام mycollection را حذف کنید:
 >db.mycollection.drop()
true
>
دوباره لیست مجموعه‌های داخل پایگاه داده را بررسی کنید:
 >show collections
mycol
system.indexes
tutorialspoint
>
اگر مجموعه انتخاب شده اه موفقیت حذف شود، متد ()drop مقدار true درغیر این صورت مقدار false را برمی‌گرداند.
مطالب
مسیریابی در Angular - قسمت دهم - Lazy loading
می‌خواهیم زمان نمایش اولین قالب برنامه را به حداقل برسانیم تا تاثیر روانی بهتری را بر روی کاربرانی که برنامه را اجرا می‌کنند، بگذاریم. برای این منظور در Angular، از Lazy loading استفاده می‌شود. همچنین این فریم ورک به همراه قابلیت پیش بارگذاری ماژول‌ها نیز هست تا سایر مسیرهای درخواستی را نیز با سرعت هرچه تمام‌تر نمایش دهد.
زمانیکه کاربری برنامه‌ی تک صفحه‌ای وب را در مرورگر باز می‌کند، ابتدا فایل index.html را در پاسخ دریافت خواهد کرد. این فایل تعاریف مداخل مورد نیاز برای رندر آن‌را مانند فایل‌های جاوا اسکریپت و CSS، به همراه دارد. سپس این فایل‌ها توسط مرورگر از سرور دریافت می‌شوند. در این حالت با پردازش این فایل‌ها، کامپوننت ریشه‌ی سایت بارگذاری می‌شود. پس از پایان آن، قالب این کامپوننت به کاربر نمایش داده خواهد شد. بنابر سرعت دریافت فایل‌ها توسط کاربر، این آغاز می‌تواند اندکی کند باشد. البته با رعایت نکات گفته‌ی شده‌ی در مطلب «Angular CLI - قسمت پنجم - ساخت و توزیع برنامه» می‌توان این حجم را توسط AoT و Tree-Shaking به میزان قابل ملاحظه‌ای کاهش داد. به علاوه با فعالسازی Lazy loading می‌توان قسمت‌های مختلف برنامه را تبدیل به یک سری Bundle کرد که در زمان درخواست، بارگذاری می‌شوند. به این ترتیب حجم فایل‌های ابتدایی که باید از سرور دریافت شوند بسیار کمتر شده و به علاوه با کاهش این حجم، مرورگر نیز باید میزان کمتری از کدها را در جهت نمایش اولین کامپوننت، پردازش و اجرا کند. در این حالت زمانیکه کاربری شروع به پیمایش مسیر یک ماژول خاص را می‌کند، آنگاه فایل‌های مرتبط با آن از سرور دریافت و در مرورگر پردازش می‌شوند. بنابراین اگر کاربری به قسمتی دسترسی ندارد، نیازی هم به دریافت فایل‌های آن نخواهد داشت؛ چون کار به فعالسازی مسیریابی آن ماژول نمی‌رسد.


آماده شدن جهت Lazy loading

پیش از Lazy loading یک قسمت از برنامه (که به آن async routing هم می‌گویند)، این قسمت باید دارای شرایطی باشد:
 - این قسمت از برنامه حتما باید در یک ماژول تعریف شده باشد. از این جهت که Lazy loading، لیست کامپوننت‌های قید شده‌ی در تعریف یک ماژول را بارگذاری می‌کند.
 - تمام مسیرهای این ماژول باید در ذیل یک مسیر والد، گروه بندی شده باشند. از این جهت که Lazy loading فقط بر روی مسیر ریشه‌ی والد تنظیم و بارگذاری می‌شود.
 - این ماژول نباید در هیچ ماژول دیگری import شده باشد. اگر این ماژول ارجاعی را در سایر ماژول‌ها داشته باشد، هیچ راهی بجز دریافت و کامپایل کامل آن توسط Angular وجود نخواهد داشت.


در مثال جاری این سری:
 - تمام ویژگی‌های قسمت مدیریت محصولات، داخل ماژول product.module.ts تعریف شده‌اند. بنابراین اولین شرط Lazy loading آن برقرار است.
 - در فایل product-routing.module.ts، کار گروه بندی مسیریابی‌ها ذیل یک والد مشخص انجام شده‌است (همان قسمت ششم این سری). بنابراین شرط دوم lazy loading این ماژول نیز پیشتر پیاده سازی شده‌است.
 - اما اگر به فایل src\app\app.module.ts مراجعه کنیم، ارجاعی به این ماژول در قسمت imports آن وجود دارد. بنابراین باید این ارجاع را حذف کنیم. در غیراینصورت کار دریافت کامل آن به همراه سایر ماژول‌های برنامه، در همان ابتدای کار صورت خواهد گرفت.
بنابراین در فایل src\app\app.module.ts، ابتدا import فایل آن‌را از ابتدای ماژول حذف و سپس ارجاع به نام کلاس کامپوننت ProductModule را نیز حذف می‌کنیم. در این حالت اگر از طریق منوی سایت سعی در دسترسی به این مسیرها کنیم، خطای 404 را دریافت خواهیم کرد؛ چون اکنون برنامه اطلاعاتی را در مورد نحوه‌ی مسیریابی قسمت محصولات برنامه، ندارد.

 
Lazy loading یک ماژول

برای بارگذاری غیرهمزمان یک ماژول و یا همان Lazy loading، می‌توان از خاصیت loadChildren تنظیمات مسیریابی، استفاده کرد:
{
   path: 'products',
   loadChildren:'app/product/product.module#ProductModule'
},
مقدار خاصیت loadChildren به صورت ذکر مسیر ماژول مرتبط به همراه یک # و سپس ذکر نام کلاس ماژول آن انجام می‌شود. مسیری هم که در اینجا ذکر می‌شود بر اساس محل قرارگیری فایل index.html، مقدار دهی شود.
با این تنظیم، زمانیکه مسیر ریشه‌ی produtcs درخواست شد، کار بارگذاری ماژول آن صورت گرفته و تنظیمات مسیریابی آن به سیستم اضافه می‌شود. به علاوه کار فعالسازی و نمایش کامپوننت آن را نیز انجام خواهد داد.

به همین منظور فایل src\app\app-routing.module.ts را گشوده و تنظیم فوق را به آن اضافه می‌کنیم:
const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  { path: 'products', loadChildren: 'app/product/product.module#ProductModule' },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
در این حالت اگر دستور ng serve -o را صادر کنید، خروجی آن اندکی متفاوت خواهد بود:
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 165 kB {4} [initial]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 32.7 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 129 kB {4} [initial]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.72 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry]
chunk    {5} 5.chunk.js, 5.chunk.js.map 51.1 kB {1} [rendered]
مورد {5} با فعالسازی lazy loading به لیست فایل‌های موجود اضافه شده‌است. این فایلی است که تنها درصورت درخواست مسیر نمایش لیست محصولات، توسط مرورگر دریافت خواهد شد و هیچ ارجاع مستقیمی به آن در فایل index.html تولیدی نهایی وجود ندارد.

به علاوه اگر در منوی سایت بر روی لینک نمایش لیست محصولات کلیک کنیم، هنوز خروجی نمایش داده نمی‌شود (هرچند خطای 404 را هم دریافت نمی‌کنیم). علت اینجا است که اگر به فایل src\app\product\product-routing.module.ts مراجعه کنیم، تعریف این مسیر ریشه، در این فایل نیز وجود دارد:
const routes: Routes = [
  {
    path: 'products',
    canActivate: [ AuthGuard ],
    children: [   ]
  }
];
بنابراین اکنون برای دسترسی به آن باید مسیر products/products را درخواست داد. به همین جهت، path و canActivate آن‌را حذف کرده و هر دو را به فایل src\app\app-routing.module.ts منتقل می‌کنیم:
import { AuthGuard } from './user/auth.guard';

const routes: Routes = [
  { path: 'home', component: WelcomeComponent },
  { path: 'welcome', redirectTo: 'home', pathMatch: 'full' },
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    canActivate: [AuthGuard]
  },
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

یک نکته: اکنون تنظیمات مسیریابی فایل src\app\product\product-routing.module.ts چنین شکلی را پیدا کرده‌است:
const routes: Routes = [
  {
    path: '',
    component: ProductListComponent
  },
  {
    path: ':id',
    component: ProductDetailComponent,
    resolve: { product: ProductResolverService }
  },
  {
    path: ':id/edit',
    component: ProductEditComponent,
    resolve: { product: ProductResolverService },
    canDeactivate: [ProductEditGuard],
    children: [
      { path: '', redirectTo: 'info', pathMatch: 'full' },
      { path: 'info', component: ProductEditInfoComponent },
      { path: 'tags', component: ProductEditTagsComponent }
    ]
  }
];
تنظیمات مسیر والد به طور کامل حذف شده‌اند. به علاوه دیگر نیازی به ذکر خاصیت children آن نیست و تمام تنظیمات مسیریابی فرزندان، داخل [] اصلی قرار گرفته‌اند. همچنین دیگر نیازی به الحاق AuthGuard در ابتدای importهای این ماژول نیست؛ چون به فایل src\app\app-routing.module.ts منتقل شده‌است.

در این حالت اگر مسیر نمایش لیست محصولات را درخواست دهیم، مشاهده خواهیم کرد فایل 5.chunk.js که حاوی اطلاعات این ماژول است، به صورت مجزایی بارگذاری شده (lazy loading) و سپس با فعال شدن محافظ مسیر آن، صفحه‌ی لاگین نمایش داده می‌شود:


این بارگذاری با تاخیر و در صورت نیاز، به دو علت آغاز برنامه را سریعتر می‌کند:
الف) مرورگر اطلاعی از وجود فایل 5.chunk.js در ابتدای کار نداشته و آن‌را بارگذاری نمی‌کند (دریافت حجم کمتر، در آغاز نمایش برنامه).
ب) چون حجم کمتری از کدهای جاوا اسکریپت توسط مرورگر در آغاز کار دریافت می‌شود، کار پردازش و اجرای آن‌ها نیز بسیار سریعتر خواهد شد.


بررسی محافظ canLoad

تعدادی از محافظ‌های مسیرها را در قسمت قبل بررسی کردیم. هنگامیکه کامپوننت‌ها به صورت lazy loading فعالسازی شده و قالب آن‌ها نمایش داده می‌شوند، می‌توان از محافظ مسیر دیگری به نام canLoad نیز استفاده کرد و هدف از آن، بررسی منطقی، پیش از فعالسازی یک مسیر غیرهمزمان است. بنابراین اگر این محافظ false را برگرداند، حتی فایل‌های اسکریپت این ماژول، بارگذاری اولیه نیز نخواهد شد. به این ترتیب کسانیکه دسترسی به یک مسیر را نداشته باشند، فایل‌های اسکریپت متناظر با آن‌را نیز دریافت نخواهند کرد.

در مثال جاری، اگر به برگه‌ی network ابزار developer مرورگر دقت کنید، با درخواست نمایش مسیر لیست محصولات، ابتدا فایل js آن دریافت می‌شود که حاوی اطلاعات تمام کامپوننت‌ها و قالب‌های مرتبط با این مسیر است و سپس صفحه‌ی login نمایش داده خواهد شد. بنابراین اگر کاربر به این قسمت دسترسی نداشته باشد، فایل js آن بی‌جهت دریافت و بارگذاری شده‌است. برای بهبود این وضعیت می‌توان نمایش لاگین را پیش از بارگذاری فایل js این ماژول فعالسازی کرد و این مورد هدف اصلی محافظ canLoad است.

در ادامه برای تکمیل مثال جاری، می‌توان AuthGuard را طوری تنظیم کرد که علاوه بر پیاده سازی CanActivate، اینترفیس CanLoad را نیز پیاده سازی کند:
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router, CanLoad, Route } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate, CanLoad {

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.checkLoggedIn(state.url);
  }

  canLoad(route: Route): boolean {
    return this.checkLoggedIn(route.path);
  }

 // … the same as before

}
همانطور که ملاحظه می‌کنید، متد canLoad بر خلاف متد canActivate دسترسی به سرویس‌های اطلاعات مسیریابی و وضعیت مسیریابی را ندارد؛ از این جهت که هنوز در این مرحله، ماژول درخواستی حاوی تنظیمات مسیریابی، بارگذاری و فعالسازی نشده‌است.

مرحله‌ی بعد، تغییر فایل src\app\app-routing.module.ts و جایگزین کردن تعریف فعلی canActivate با canLoad است:
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    canLoad: [AuthGuard]
  },
پس از این تغییر، برنامه را مجدا اجرا کرده و صفحه را refresh کنید. سپس برگه‌ی network ابزار developers را نیز باز نگه دارید. اکنون بر روی لینک نمایش لیست محصولات کلیک کنید. مشاهده خواهید کرد که در این حالت صفحه‌ی لاگین، بدون بارگذاری ماژول Js ایی نمایش داده می‌شود. در ادامه اگر لاگین کنیم، آنگاه فایل js این ماژول توسط مرورگر دریافت شده و بارگذاری می‌شود.


پیش بارگذاری ماژول‌ها

با فعالسازی lazy loading، ماژول‌های مورد نیاز کاربر دیگر به همراه فایل‌های js ابتدایی برنامه که در فایل index.html ارجاع مستقیمی به آن‌ها دارند، ارائه نمی‌شوند و تنها در صورت درخواست مشاهده‌ی مسیری، کار بارگذاری آن‌ها توسط برنامه صورت خواهد گرفت. همین مساله می‌تواند در بار اول نمایش این ماژول‌ها تاخیر کوتاهی را سبب شود. به همین جهت قابلیت پیش بارگذاری ماژول‌ها نیز در سیستم مسیریاب Angular پیش بینی شده‌است. به این قابلیت preloading و یا eager lazy loading نیز می‌گویند. در این حالت برنامه در پشت صحنه، کار پیش واکشی ماژول‌ها را انجام می‌دهد و زمانیکه کاربری مسیری را درخواست می‌دهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.
بدیهی است این قابلیت نباید برای ماژول‌هایی که قرار است توسط کاربرانی خاص مشاهده شوند فعال شود و هدف آن دسترسی سریع به ماژول‌های پرکاربرد برنامه‌است.

در اینجا سه استراتژی پیش بارگذاری ماژول‌ها میسر است:
 - No preloading که حالت پیش فرض است.
 - Preload all سبب پیش بارگذاری تمام قسمت‌های lazy load برنامه می‌شود.
 - Custom که اجازه‌ی تعریف یک استراتژی سفارشی را می‌دهد.

برای مثال برای فعالسازی حالت Preload all، باید به فایل src\app\app-routing.module.ts مراجعه کرده و تغییرات ذیل را اعمال کنیم:
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

@NgModule({
  imports: [RouterModule.forRoot(
    routes,
    { enableTracing: true, preloadingStrategy: PreloadAllModules  /*, useHash: true*/ }
  )],
در اینجا نحوه‌ی تنظیم preloadingStrategy را به PreloadAllModules مشاهده می‌کنید. در این حالت پس از آغاز ابتدایی برنامه، مسیریاب بلافاصله تمام مسیرهای lazy load را در پشت صحنه بارگذاری می‌کند.

یک نکته: وجود محافظ canLoad، هر نوع استراتژی prealoading را غیرفعال می‌کند. اما prealoading با سایر انواع محافظ‌ها کار می‌کند.
بنابراین برای آزمایش تنظیم  preloadingStrategy: PreloadAllModules، تعریف canLoad را به canActivate تغییر دهید.


تعریف استراتژی‌های سفارشی پیش بارگذاری ماژول‌ها

اگر نیاز به یک استراتژی پیش بارگذاری بهتر از هیچ یا همه باشد، می‌توان یک استراتژی سفارشی را نیز تدارک دید و ایجاد آن سه مرحله‌ی ایجاد سرویس مرتبط، ثبت آن سرویس در ماژول و در آخر تنظیم مسیریابی را به همراه دارد.
برای این منظور ابتدا دستور ذیل را صادر کنید تا قالب ابتدایی سرویس SelectiveStrategy ایجاد شود:
 >ng g s SelectiveStrategy -m app.module
که سبب تولید و به روز رسانی فایل‌های ذیل در پوشه‌ی src\app خواهد شد (چون مرتبط است به کل برنامه):
 installing service
  create src\app\selective-strategy.service.spec.ts
  create src\app\selective-strategy.service.ts
  update src\app\app.module.ts
در این حالت لیست providers فایل app.module.ts نیز به صورت خودکار تکمیل می‌گردد.

سپس کدهای SelectiveStrategyService را به نحو ذیل تغییر دهید:
import { Injectable } from '@angular/core';
import { Route, PreloadingStrategy } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';

@Injectable()
export class SelectiveStrategyService implements PreloadingStrategy {

  preload(route: Route, load: Function): Observable<any> {
    if (route.data && route.data['preload']) {
      return load();
    }
    return Observable.of(null);
  }
}
- این سرویس ویژه باید اینترفیس PreloadingStrategy را پیاده سازی کند. سپس باید متد اجباری preload آن‌را افزود و تکمیل نمود.
- پارامتر اول این متد، اطلاعاتی را در مورد مسیر جاری در اختیار ما قرار می‌دهد و دومین پارامتر آن متدی است که کار preloading را انجام می‌دهد.
- در اینجا است که تصمیم می‌گیریم ماژولی را preload کنیم یا خیر. برای نمونه در اینجا از خاصیت data مسیریابی استفاده شده‌است. این خاصیت نیز به یک مقدار ثابت اشاره می‌کند (قسمت «ارسال اطلاعات ثابت به مسیرهای مختلف برنامه» قسمت چهارم). برای مثال نام دلخواه آن‌را preload گذاشته‌ایم و اگر مقدار آن به true تنظیم شده بود، آنگاه این مسیر preload خواهد شد. فراخوانی متد load در اینجا به معنای preloading این مسیر است. در غیراینصورت null را بازگشت می‌دهیم.


در ادامه نیاز است در فایل src\app\app-routing.module.ts، بجای معرفی PreloadAllModules، این استراتژی سفارشی خود را معرفی کرد:
import { SelectiveStrategyService } from './selective-strategy.service';

@NgModule({
  imports: [RouterModule.forRoot(
    routes,
    { enableTracing: true, preloadingStrategy: SelectiveStrategyService
     /*, preloadingStrategy: PreloadAllModules*/  /*, useHash: true*/ }
  )],
و همچنین تعریف مسیریابی برنامه به این صورت تغییر می‌کند:
  {
    path: 'products',
    loadChildren: 'app/product/product.module#ProductModule',
    //canLoad: [AuthGuard] 
    canActivate: [AuthGuard],
    data: { preload: true }
  },
در اینجا نحوه‌ی مقدار دهی خاصیت data را به اطلاعات ثابت preload: true مشاهده می‌کنید. این اطلاعاتی است که در سرویس SelectiveStrategy سفارشی ما بررسی شده و بر اساس آن در مورد پیش بارگذاری این مسیر تصمیم‌گیری می‌شود.

برای آزمایش آن، برنامه را مجدا اجرا کرده و صفحه را refresh کنید. سپس برگه‌ی network ابزار developers را نیز باز نگه دارید. مشاهده خواهید کرد که علاوه بر فایل‌های js اصلی برنامه که در فایل index.html ارجاعی را دارند، فایل 5.chunk.js نیز پیش بارگذاری شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-09.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
React 16x - قسمت 24 - ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
پس از آشنایی با مقدمات کار با Axios، در این قسمت امکانات پیشرفته‌تر آن‌را مانند خطایابی سراسری، interceptors  و ... بررسی می‌کنیم.


به روز رسانی‌های خوشبینانه‌ی UI

پیاده سازی اعمال CRUD توسط Axios در قسمت قبل، به همراه یک مشکل مهم است: اعمال کار با شبکه و سرور، زمانبر هستند و مدتی طول می‌کشد تا پاسخ عملیات از سمت سرور دریافت شود. در این بین اگر خطایی رخ دهد، مابقی کدهای نوشته شده‌ی در متدهایی مانند Update و Delete، اجرا نمی‌شوند. به این حالت «به روز رسانی بدبینانه‌ی UI» گفته می‌شود. در حالت خوشبینانه، فرض بر این است که در اکثر موارد، فراخوانی سرور با موفقیت به پایان می‌رسد. در یک چنین حالتی، ابتدا UI به روز رسانی می‌شود و سپس فراخوانی‌های سمت سرور صورت می‌گیرند. اگر این فراخوانی با شکست مواجه شد، مجددا UI را به حالت قبلی آن باز می‌گردانیم:
  handleDelete = async post => {
    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });

    await axios.delete(`${apiEndpoint}/${post.id}`);
  };
در کدهای فوق، ابتدا UI به روز رسانی می‌شود (که بسیار سریع است)، سپس حذف سمت سرور صورت می‌گیرد. یک چنین پیاده سازی، به کاربر حس کار با یک برنامه‌ی بسیار سریع را القاء می‌کند؛ هرچند فراخوانی سمت سرور انجام شده، ممکن است مدتی طول بکشد.
اما اگر در این بین خطایی رخ داد، چه باید کرد؟ باید آخرین تغییر انجام شده را به حالت اول باز گرداند. انجام یک چنین کاری در React ساده‌است. چون ما state را به صورت مستقیم ویرایش نمی‌کنیم، همیشه می‌توان ارجاعی را به state قبلی، ذخیره و سپس در صورت نیاز آن‌را بازیابی کرد:
  handleDelete = async post => {
    const originalPosts = this.state.posts;

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts }); // Optimistic Update

    try {
      await axios.delete(`${apiEndpoint}/${post.id}`);
    } catch (ex) {
      alert("An error occurred when deleting a post!");
      this.setState({ posts: originalPosts }); // Undo changes
    }
  };
در اینجا در ابتدا توسط متغیر originalPosts، ارجاعی را به وضعیت قبلی آرایه‌ی posts موجود در state (وضعیت ابتدایی UI)، نگهداری می‌کنیم. سپس کار حذف بسیار سریع آیتم درخواستی را از UI انجام می‌دهیم. اکنون کار حذف اصلی رکورد را از سرور، درون یک try/catch انجام خواهیم داد. اگر خطایی رخ دهد، پیامی را به کاربر نمایش داده و سپس مجددا state را به همان originalPosts پیشین، باز خواهیم گرداند.


مدیریت خطاهای رخ داده‌ی در حین فراخوانی سرور

تا اینجا مشاهده کردیم که یک روش مدیریت خطاها در کدهای Axios، قرار دادن آن‌ها در یک قطعه کد try/catch است. در اینجا نیز باید بتوان بین خطاهای پیش بینی شده و نشده، تفاوت قائل شد.
- خطاهای پیش بینی شده: برای مثال اگر درخواست حذف رکوردی را دادیم که در بانک اطلاعاتی موجود نیست، انتظار داریم سرور، خطای 404 یا return NotFound را بازگشت دهد و یا 400 که معادل bad request است و در حالت ارسال داده‌هایی غیرمعتبر، رخ می‌دهد. در این موارد بهتر است خطاهایی خاص را به کاربران نمایش داد؛ برای مثال رکورد درخواستی وجود ندارد یا پیشتر حذف شده‌است.
- خطاهای پیش بینی نشده: این نوع خطاها نباید و یا قرار نیست در شرایط عادی رخ دهند. برای مثال اگر شبکه در دسترس نیست، امکان ارتباط با سرور نیز میسر نخواهد بود و یا حتی ممکن است خطایی در کدهای سمت سرور، سبب بروز خطایی شده باشد. این نوع خطاها ابتدا باید لاگ شوند تا با بررسی‌های آتی آن‌ها، بتوان مشکلات پیش بینی نشده را بهتر برطرف کرد. همچنین در یک چنین مواردی، باید یک پیام خطای خیلی عمومی را به کاربر نمایش داد؛ برای مثال «یک خطای پیش بینی نشده رخ داده‌است.».

برای مدیریت این دو حالت باید به جزئیات شیء ex، در بدنه‌ی catch، دقت کرد که دارای دو خاصیت request و response است. اگر ex.response تنظیم شده بود، یعنی دریافت خروجی از سرور موفقیت آمیز بوده‌است. اگر سرور در دسترس نباشد و یا برنامه‌ی سمت سرور کرش کرده باشد، ex.response نال خواهد بود. اگر ex.request نال نبود، یعنی ارسال درخواست به سمت سرور با موفقیت انجام شده‌است. برای مثال جهت بررسی خطای مورد انتظار 404، می‌توان در قسمت catch(ex) به صورت زیر عمل کرد:
try {
  await axios.delete(`${apiEndpoint}/${post.id}`);
} catch (ex) {
  if (ex.response && ex.response.status === 404) {
     alert("This post has already been deleted!");
  } else {
     console.log("Error", ex);
     alert("An unexpected error occurred when deleting a post!");
  }

  this.setState({ posts: originalPosts }); // Undo changes
}
در اینجا ابتدا بررسی می‌شود که آیا شیء response نال است یا خیر؟ سپس خاصیت status آن‌را برای بررسی خطاهای پیش بینی شده، بررسی می‌کنیم. خطایی که در اینجا نمایش داده می‌شود، اختصاصی‌تر است. در غیراینصورت، ابتدا باید این خطا لاگ شود و سپس یک اخطار عمومی نمایش داده می‌شود. پس از بررسی هر دو حالت، باید UI را مجددا به حالت اول آن بازگشت داد.
عموما خطاهای پیش‌بینی شده را لاگ نمی‌کنیم؛ چون ممکن است کاربر، یک صفحه را در چندین برگه باز کرده باشد و در یکی، رکوردی را حذف کند. در این حال، این رکورد هنوز در برگه‌های دیگر موجود است و اگر مجددا درخواست حذف آن‌را صادر کند، مشکل خاصی از دیدگاه برنامه رخ نداده‌است و نیازی به پیگیری‌های آتی را ندارد. یعنی صرفا یک client error است.


مدیریت سراسری خطاهای رخ داده‌ی در حین فراخوانی سرور

برای مدیریت خطاها، نیاز است یک چنین try/catchهایی را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، قرار دهیم. برای کاهش این کدهای تکراری، از interceptors کتابخانه‌ی Axios استفاده می‌شود. در این کتابخانه می‌توان در جاهائیکه درخواستی به سمت سرور ارسال می‌شود و یا پاسخی از سمت سرور دریافت می‌شود، قطعه کدهایی سراسری را قرار داد و بر روی درخواست و یا پاسخ، تغییراتی را اعمال کرد و یا حتی اطلاعات مربوطه را لاگ کرد؛ به این نوع قطعه کدها، interceptor گفته می‌شود و برای تعریف آن‌ها می‌توان از axios.interceptors.request و یا axios.interceptors.response، خارج از کلاس جاری استفاده کرد. برای مثال بر روی شیء axios.interceptors.response، می‌توان متد use را فراخوانی کرد که دو پارامتر را که هر کدام یک callback function هستند، می‌پذیرد. اولی در صورت موفقیت آمیز بودن response فراخوانی می‌شود و دومی در صورت شکست آن. اگر نیازی به هر کدام نبود، می‌توان آن‌را به null مقدار دهی کرد. اگر مدیریت قسمت شکست علمیات مدنظر است، نیاز خواهد بود در پایان این callback function، یک Rejected Promise را بازگشت داد تا ادامه‌ی برنامه، به درستی مدیریت شود. در این حالت اگر خطایی رخ دهد، ابتدا این interceptor فراخوانی می‌شود و سپس کنترل به بدنه‌ی catch منتقل خواهد شد:
import "./App.css";

import axios from "axios";
import React, { Component } from "react";

axios.interceptors.response.use(null, error => {
  console.log("interceptor called.");
  return Promise.reject(error);
});

const apiEndpoint = "https://localhost:5001/api/posts";
class App extends Component {
اکنون می‌خواهیم قطعه کد نمایش خطاهای عمومی پیش بینی نشده را از تمام بدنه‌های catch حذف کرده و به یک interceptor منتقل کنیم:
axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});
خطاهای پیش بینی شده عموما در بازه‌ی 400 تا 500 قرار دارند. به همین جهت اگر یک چنین خطاهایی را دریافت کردیم، اخطاری را نمایش نداده و صرفا کنترل را به catch block منتقل می‌کنیم. اما اگر خطا، پیش بینی نشده بود، کار لاگ کردن خطا و همچنین نمایش اخطار را در اینجا انجام خواهیم داد.

یک نکته: استفاده از try/catchها فقط برای بازگشت UI به حالت قبلی و یا نمایش خطایی خاص به کاربر توصیه می‌شوند. اگر از روش «به روز رسانی‌های خوشبینانه‌ی UI» استفاده نمی‌کنید و همچنین خطاهای ویژه‌ای بجز خطای عمومی لاگ شده‌ی در interceptor فوق مدنظر شما نیست، نیازی هم به try/catch نخواهد بود و پس از بروز خطا، قسمت‌های بعدی کد اجرا نمی‌شوند؛ اما خطای عمومی فوق نمایش داده خواهد شد.


ایجاد یک HTTP Service با قابلیت استفاده‌ی مجدد

تا اینجا تعریف interceptor را پیش از کلاس کامپوننت جاری قرار داده‌ایم که هم سبب شلوغی این ماژول شده‌است و هم در صورت نیاز به آن در سایر برنامه‌ها، باید همین قطعه کد را مجددا در آن‌ها کپی کرد. به همین جهت پوشه‌ی جدید src\services را ایجاد کرده و سپس فایل src\services\httpService.js را در آن با محتوای زیر ایجاد می‌کنیم:
import axios from "axios";

axios.interceptors.response.use(null, error => {
  const expectedError =
    error.response &&
    error.response.status >= 400 &&
    error.response.status < 500;

  if (!expectedError) {
    console.log("Error", error);
    alert("An unexpected error occurred when deleting a post!");
  }

  return Promise.reject(error);
});

export default {
  get: axios.get,
  post: axios.post,
  put: axios.put,
  delete: axios.delete
};
در اینجا علاوه بر انتقال interceptor تعریف شده، کار export متدهای axios نیز به صورت یک شیء جدید صورت گرفته‌است.
سپس به app.js مراجعه کرده و این ماژول را با یک نام دلخواه import می‌کنیم:
import http from "./services/httpService";
در ادامه هرجائیکه ارجاعی به axios وجود دارد، آن‌را با http فوق جایگزین می‌کنیم. در این حالت می‌توان "import axios from "axios را نیز از ابتدای app.js حذف کرد. مزیت اینکار، مخفی کردن Axios، در پشت صحنه‌ی ماژول جدیدی است که ایجاد کردیم. به این ترتیب اگر در آینده خواستیم، Axios را با کتابخانه‌ی دیگری جایگزین کنیم، در کل برنامه تنها نیاز است این httpService.js جدید را تغییر دهیم.


ایجاد یک ماژول Config

بهبود دیگری را که می‌توانیم اعمال کنیم، انتقال const apiEndpoint تعریف شده، به یک ماژول مجزا است؛ تا اگر نیاز به استفاده‌ی از آن در قسمت‌های دیگری نیز وجود داشت، به سادگی بتوان آن‌را مدیریت کرد. به همین جهت فایل جدید src\config.json را با محتوای زیر ایجاد می‌کنیم:
{
   "apiEndpoint" : "https://localhost:5001/api/posts"
}
سپس به فایل app.js بازگشته و ابتدا const apiEndpoint را حذف و سپس import زیر را به ابتدای فایل، اضافه می‌کنیم:
import config from "./config.json";
اکنون هر جائی در کدهای خود که apiEndpoint را داریم، تبدیل به config.apiEndpoint می‌کنیم.


نمایش بهتر خطاها به کاربر توسط کتابخانه‌ی react-toastify

بجای alert توکار مرورگرها، می‌توان یک صفحه‌ی دیالوگ زیباتر را برای نمایش خطاها درنظر گرفت. به همین جهت ابتدا کتابخانه‌ی react-toastify را نصب می‌کنیم:
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
اکنون به src\services\httpService.js مراجعه کرده و alert آن‌را به صورت زیر تغییر می‌دهیم:
import { toast } from "react-toastify";
// ...

axios.interceptors.response.use(null, error => {
  // ...
  if (!expectedError) {
    // ...
    toast.error("An unexpected error occurrred.");
  }
ابتدا، شیء toast آن import می‌شود و سپس توسط این شیء می‌توان از متد error آن، جهت نمایش خطاهایی شکیل‌تر استفاده کرد؛ با این خروجی:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-03.zip و sample-22-frontend-part-03.zip
مطالب
SQL Instance
ممکن است کاربر بر روی سیستم خود نسخه‌های مختلفی از SQL Server را نصب کرده باشد. برای مثال SQL Express, SQL 2005, SQL 2008. و یا نسخه ای خاص (مثلا 2012) را چند بار روی سیستم خود نصب کرده باشد. SQL برای تفکیک این نسخه‌ها و نصب‌ها از مفهومی با عنوان Instance استفاده می‌کند. یعنی به هر نسخه نصب شده نامی یکتا می‌دهد تا بتوان به تفکیک به آنها دسترسی داشت.
برای اتصال به این نسخه‌ها باید در بخش آدرس سرور، از ترکیب نام سیستم و نام Instance به این شکل استفاده کرد:  SystemName\Instance
بعضی مواقع لازم است که لیست Instance‌های نصب شده روی سیستم کاربر را به دست آوریم. ADO.NET کلاسی به همین منظور تعبیه کرده که شبکه را جستجو کرده و SQL Instance‌های مختلف را که قابل دسترسی هستند را برای شما لیست می‌کند. استفاده از این کلاس بسیار ساده است:
using System.Data.Sql;

class Program
{
  static void Main()
  {
    // Retrieve the enumerator instance and then the data.
    SqlDataSourceEnumerator instance =
      SqlDataSourceEnumerator.Instance;
    System.Data.DataTable table = instance.GetDataSources();

    // Display the contents of the table.
    DisplayData(table);

    Console.WriteLine("Press any key to continue.");
    Console.ReadKey();
  }

  private static void DisplayData(System.Data.DataTable table)
  {
    foreach (System.Data.DataRow row in table.Rows)
    {
      foreach (System.Data.DataColumn col in table.Columns)
      {
        Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
      }
      Console.WriteLine("============================");
    }
  }
}

البته با توجه به اینکه شبکه را جستجو می‌کند در نرم افزار شما وقفه خواهد انداخت. خوب اگه بخواهیم Instance‌های نصب شده روی سیستم کاربر را پیدا کنیم چی؟ ساده‌ترین و سریعترین راه استفاده از رجیستری سیستم است. نام Instance‌ها در رجیستری ویندوز در آدرس زیر قابل دسترسی است:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names

برای استفاده از این کلید در c# می‌توان از کد زیر کمک بگیرید:
            var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names");

            foreach (string sk in key.GetSubKeyNames())
            {
                var rkey = key.OpenSubKey(sk);
                foreach (string s in rkey.GetValueNames())
                {
                    MessageBox.Show("Sql instance name:" + s);
                }
            }
فقط دو نکته قابل توجه است. برنامه باید در Any CPU کامپایل شود تا در سیستم‌های 64 بیتی بتوانید به محل درست رجیستری دسترسی پیدا کنید. چون نرم افزارهای 32 بیت در ویندوز 64 بیت در سیستم wow64 اجرا می‌شود که دسترسی به رجیستری آن در آدرس wow64 هر قسمت رجیستری است. بنابراین کد فوق در حالت Any CPU و غیر فعال بودن Prefer 32-bit قسمت Build در Properties برنامه به درستی اجرا می‌شود.
نکته: Default Instance در SQL مقدار MSSQLSERVER  می‌باشد.
مطالب
ایجاد قسمت‌های Toggle در سایت با jQuery
البته قبلش بگم که عنوان بهتری به ذهنم نرسید.
بسیاری از مواقع پیش می‌آید که در سایت خود بخواهیم کادری داشته باشیم که با کلیک بروی آن ظاهر و با کلیک دوباره بروی آن محو شود. مانند تصویر زیر

سپس با کلیک بروی قسمت مشخص شده از تصویر بالا تصویر مانند زیر ظاهر شود.

در این نوشته قصد داریم کادری به این صورت حالا به هر منظوری طراحی نماییم.

برای کار سه قسمت کد داریم:

  1. کدهای طراحی قسمت مورد نظر در صفحه وب
  2. نوشتن کدهای CSS مربوطه
  3. نوشتن کدهای jQuery

در مرحله اول ابتدا صفحه وب خود را به نحو زیر ایجاد می‌نماییم.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>کادر لغزان با jQuery</title>
    <script src="Scripts/jquery-1.7.1.min.js"></script>
    <link href="CSS/site.css" rel="stylesheet" />
</head>
<body>
    <div id="loginPanel">
        <div style="height: auto;" id="login">
            <div>
                <div>
                    <br />
                    محتویات دلخواه خود را در این قسمت قرار دهید
                </div>
            </div>
            <div><a href="#" id="closeLogin"></a></div>
        </div>
        <div id="container">
            <div id="top">
                <!-- login -->
                <ul>
                    <li>&nbsp;</li>
                    <li><a id="toggleLogin" href="#">پانل باز شو</a></li>
                </ul>
                <!-- / login -->
            </div>
            <!-- / top -->

        </div>
    </div>
    <div id="main">
        محتویات سایت در این قسمت قرار می‌گیرد
    </div>
    </body>
</html>
در صفحه ایجاد شده قسمتی را برای نگهداری پانل مورد نظر قرار دادیم و در div با نام loginContent مواردی را که می‌خواهیم در پانل مربوطه نمایش داده شود، قرار می‌دهیم،  <"div id="loginPanel> نگهدارنده کل قسمت مربوطه (کادر لغزان می‌باشد)، و قسمت <"div id="container> قسمتی است که دکمه یا عنوان مورد نظر برای باز شدن یا بستن کادر استفاده می‌شود.
در مرحله دوم کدهای CSS بخش‌های مورد نظر (جهت رنگ و تصاویر و شکل و شمایل کادر مورد نظر) را مانند زیر ایجاد می‌کنیم.
body {
     margin:0; 
     padding:0; 
     width:100%; 
     background: #e9e9e9 url(images/header_bg.gif) top repeat-x;
     direction: rtl;
}
html {
     padding:0; 
     margin:0;
}

#main {margin-top: 100px;}

#loginPanel {
    margin: 0px; 
    position: absolute; 
    overflow: hidden; 
    height: auto;
    z-index: 3000;
    width: 100%;
    top: 0px;
    color: #fff;
}
#top {
    background: url(images/login_top.jpg) repeat-x 0 0;
    height: 38px;
    position: relative;
}
#top ul.login {
    display: block;
    position: relative;
    float: right;
    clear: right;
    height: 38px;
    width: auto;
    margin: 0;
    right: 150px;
    color: white;
    text-align: center;
    background: url(images/login_r.png) no-repeat right 0;
    padding-right: 45px;
}
#top ul.login li.left {
    background: url(images/login_l.png) no-repeat left 0;
    height: 38px;
    width: 45px;
    padding: 0;
    margin: 0;
    display: block;
    float: left;
}
#top ul.login li {
    text-align: left;
    padding: 0 6px;
    display: block;
    float: left;
    height: 38px;
    background: url(images/login_m.jpg) repeat-x 0 0;
}
#top ul.login li a {
    color: #fff;
    text-decoration: none;
}
#top ul.login li a:hover {
    color: #ff0000;
    text-decoration: none;
}
#login {
    width: 100%;
    color: white;
    background: #1E1E1E;
    overflow: hidden;
    position: relative;
    z-index: 3;
    height: 0px;
}
#login a {
    text-decoration: none;
    color: #fff;
}
#login a:hover {
    color: white;
    text-decoration: none;
}
#login .loginContent {
    width: 900px;
    height: 80px;
    margin: 0 auto;
    padding-top: 25px;
    text-align: right;
}
#login .loginClose {
    display: block;
    position: absolute;
    right: 15px;
    top: 10px;
    width: 70px;
    text-align: left;
}
#login .loginClose a {
    display: block;
    width: 100%;
    height: 20px;
    background: url(images/button_close.jpg) no-repeat right 0;
    padding-right: 10px;
    border: none;
    color: white;
}
#login .loginClose a:hover {
    background: url(images/button_close.jpg) no-repeat right -20px;
}
.cen { text-align: center;}
.w_100p{ width: 100%;}
خوب تا اینجای کار فقط کادر با قالب مورد نظر ایجاد شد، برای اینکه عمل مورد نظر انجام شود با استفاده از تکنیک‌های jQuery به صورت زیر کار را به پایان می‌رسانیم. در انتهای صفحه اسکریپت زیر را قبل از قسمت <body/> می‌نویسیم.
 <script type="text/javascript">
        $(document).ready(function () {
            $("#login").hide(0);
            $("#toggleLogin").click(function () {
                $("#login").slideToggle("slow");
            });
            $("#closeLogin").click(function () {
                $("#login").slideUp("slow");
            });
        });
    </script>
با نوشتن این اسکریپت بعد از لود صفحه مورد نظر ابتدا کادر ما مخفی می‌شود، سپس برای دکمه (یا هر المانی که می‌خواهیم با کلیک روی آن کادر بسته یا باز شود) کد کلیک می‌نویسیم که با کلیک بروی آن عمل اسلاید (باز یا بسته شدن) رخ دهد. در نهایت در رویداد کلیک لینک close تعیین می‌کنیم که کادر به آرامی  بسته شود.
مثال کامل از ^ قابل دانلود است
لینک‌های کمکی جهت آشنایی بیشتر با توابع استفاده شده:




بازخوردهای دوره
متدهای async تقلبی
به شکل فعلی آن تقلبی هست. اصل این فرآیند مربوط است به متد وب سرویس SendGroupSmsSimple و چون تبادلات شبکه‌ای دارد (IO-bound)، باید نگارش async آن توسط ارائه کننده‌ی آن فراهم شود تا واقعا مفهوم async را پیاده سازی کند. در اینجا شما فقط یک thread اضافه‌تر ایجاد کرده‌اید (CPU-bound operation) و ایجاد یک thread به معنای وجود یک عملیات async نیست.