سه‌شنبه ۳۰ دی ۱۳۹۳ ه‍.ش.

خطای عجیب Visual Studio هنگام Build کردن پروژه

امروز هنگام Build کردن یک پروژه WPF به خطای زیر برخورد کردم:

Unable to copy file "obj\Debug\XXX.exe" to "bin\Debug\XXX.exe". The process cannot access the file 'bin\Debug\XXX.exe' because it is being used by another process

این درحالی بود که برنامه در حال اجرا نبود. با یک جستجوی ساده در وب متوجه می شوید که قدمت این خطا به Visual Studio 2003 برمی گردد. اما راه حل فعلی آن بسیار ساده است. کافیست روی پروژه راست کلیک کنید، گزینه Properties رو انتخاب کنید و در قسمت Build Events درون کادر متنی Pre-build command line عبارات زیر را وارد کنید:

if exist "$(TargetPath).locked" del "$(TargetPath).locked"
if exist "$(TargetPath)" if not exist "$(TargetPath).locked" move "$(TargetPath)" "$(TargetPath).locked"


راه حل های دیگری مثل Clean کردن Solution یا Restart کردن Visual Studio هم معرفی شده ولی هیچکدام برای من کارساز نبود.

سه‌شنبه ۲ دی ۱۳۹۳ ه‍.ش.

ایجاد ViewModel و View برای Caliburn.Micro به روش scaffolding

در صورتی که با فریم ورک Caliburn آشنایی ندارید مستندات آن را مطالعه کنید.
برای شروع ابتدا یک پروژه WPF معمولی بسازید و دستورات زیر را در پنجره Package Manager Console وارد کنید. در صورتی که این پنجره را نمی بینید از منوی View گزینه Other Windows و سپس Package Manager Console را انتخاب کنید.

Install-package Caliburn.Micro.Scaffolder

سپس صبر کنید تا همه پیش نیاز های آن که شامل EntityFramework، Caliburn.Micro، T4Scaffolding و T4Scaffolding.Core است نصب شود.

حالا یک پوشه جدید در پروژه خود بسازید و آن را Models نامگذاری کنید. سپس کلاس جدیدی به نام Person به این پوشه اضافه کنید و کد زیر را به جای کلاس خالی Person قرار دهید:

    public class Person
    {
        public int Id { get; set; }

        public string FullName { get; set; }
    }

پروژه را Build کنید.
اکنون در Package Manager Console فرمان زیر را وارد کنید:

Scaffold Add-ViewAndViewModel Person -Force

و Enter بزنید.
خب با توجه به خروجی ارائه شده باید متوجه شده باشید که چه اتفاقی افتاده. فایل های ViewModel و View متناظر درحالی که یک TextBox در آن قرار دارد و به کمک قائده نامگذاری به FullName در ViewModel متناظر Bind شده، ایجاد شده است.

این Package در واقع نسخه دست کاری شده Caliburn.Micro.Scaffolding است که به آن قابلیت هایی اضافه کرده ام. مثلا ViewModel شما تمامی Property های کلاس Model شما را پیاده سازی می کند. همینطور View تولید شده دارای فیلدهایی Bind شده به ViewModel شماست.
البته باید توجه داشته باشید که کدهای تولید شده جهت استفاده نهایی در پروژه های تجاری شما کامل نیست. مزیت استفاده از Scaffolding در این است که شما مجبور نیستید کدهای خسته کننده تکراری را برای ViewModel ها و طراحی اولیه View را انجام دهید. در عوض، با تولید خودکار این کدها به ازای هر Model، شما فقط به سفارشی کردن ViewModel و View متناظر با آن می پردازید.

یکشنبه ۱۸ آبان ۱۳۹۳ ه‍.ش.

پشتیبانی رسمی از زبان و تقویم فارسی در Orchard - نسخه پیش انتشار

پیش تر تلاش های پراکنده ای برای فارسی سازی Orchard، اصلاح تاریخ و ساخت Theme راست به چپ برای قسمت Admin انجام شده بود ولی حالا با عنایت آقای Daniel Stolt، در نسخه 1.9 این سیستم مدیریت محتوا، به صورت رسمی از زبان فارسی، تقویم ایرانی و پوسته مدیریتی راست چین شده بهرمند خواهیم شد.

سیستم مدیریت محتوای Orchard راست چین با تقویم فارسی

این تغییرات با انتشار نسخه 1.9 نهایی خواهند شد ولی هم اکنون می توانید نسخه ی در حال توسعه آن را از سایت CodePlex دریافت کنید. برای این کار به صفحه Source پروژه Orhcard بروید و پس از انتخاب انشعاب (Browsing Changes) یک.ایکس (1.x)، گزینه Download را کلیک کنید.
نکته قابل تامل در این باره، ناسازگاری dot Net Framework با تقویم فارسی است که با پیاده سازی کلاس PersianDateTimeFormatInfo حل شده است. استفاده از یک کلاس اختصاصی برای Hard Code کردن نام فارسی ماه ها جالب ترین قسمت ماجراست.
برای مشاهده لیستی از کلیه تغییرات در باره محلی سازی و رفع ایرادات به این صفحه مراجعه کنید.

شنبه ۱۴ بهمن ۱۳۹۱ ه‍.ش.

نوشتن یک Module در Prism

قبل از شروع خواندن این پست ممکن است لازم باشد با Bootstrapper در پست قبلی، همچنین الگوی طراحی MVVM آشنا باشید.

قبل از هر چیز باید Shell برنامه را برای پذیرش View های جدید از Module ها، آماده کنیم. همینطور ممکن است بخواهیم در Shell برنامه یک Ribbon داشته باشیم تا Module ها بتوانند به آن RibbonTab های خود را اضافه کنند. پس از اطمینان از دانلود و نصب آخرین نسخه Ribbon Control Library برای دات نت 4، روی پروژه PrismApp.Shell راست کلیک کنید و پس از انتخاب گزینه Add Reference، در زبانه Extensions آیتم RibbonControlsLibrary را انتخاب کنید. اگر از Visual Studio 2010 استفاده می کنید احتمالا بتوانید RibbonControlsLibrary را در این مسیر پیدا کنید:

C:\Program Files (x86)\Microsoft Ribbon for WPF\V4.0

بسیار خوب. اکنون باید کد MainWindow.xaml را به این شکل تغییر دهید:

<ribbon:RibbonWindow x:Class="PrismApp.Shell.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://www.codeplex.com/prism"
        xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
        Title="Prism App" Height="550" Width="725" Style="{StaticResource WindowStyle}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="{Binding Height, ElementName=MainRibbon}" ></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <ribbon:Ribbon x:Name="MainRibbon" Grid.Row="0" prism:RegionManager.RegionName="RibbonRegion">
            <ribbon:Ribbon.ApplicationMenu>
                <ribbon:RibbonApplicationMenu Visibility="Collapsed">
                </ribbon:RibbonApplicationMenu>
            </ribbon:Ribbon.ApplicationMenu>
        </ribbon:Ribbon>
        <TabControl prism:RegionManager.RegionName="MainRegion" Margin="5" Grid.Row="2" />
    </Grid>
</ribbon:RibbonWindow>

پنجره اصلی برنامه از RibbonWindow ساخته شده است. فراموش نکنید در CodeBehind (فایل MainWindow.xaml.cs) نیز کلاس پدر را از Window به RibbonWindow تغییر بدهید. در قسمت ارجاعات ما دو ارجاع به ribbon و prism داریم تا بتوانیم از کلاس ها و extension های آن ها در این پنجره استفاده کنیم. پس از افزودن یک Ribbon، یک TabControl نیز در داخل Grid اصلی قرار دادیم. بدین صورت هر module می تواند View های خود را تحت یک TabItem نمایش دهد. ولی چطور؟
به کمک Region ها شما می توانید برخی کنترل ها را به عنوان میزبانی برای View های دیگر معرفی کنید. بدین صورت بعدا می توانید به Region ها دسترسی پیدا کنید و View های داخل آن ها را مطابق نیاز خود عوض کنید.
هر Region باید دارای یک نام منحصر به فرد باشد. ما در این پروژه، Region ی که قرار است TabItem ها را در خود نگه دارد را MainRegion نامیدیم. (به کد TabControl دقت کنید.)

<TabControl prism:RegionManager.RegionName="MainRegion" Margin="5" Grid.Row="2" />

همینطور در کد Ribbon، یک Region به نام RibbonRegion ایجاد کردیم تا module ها بتوانند RibbonTab های خود را به آن اضافه کنند. در همین پروژه یک پوشه به نام Themes بسازید و درون آن یک ResourceDictionary به نام Generic.xaml قرار دهید. سپس Style های زیر را در آن بنویسید:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon">
    <Style TargetType="Window" x:Key="WindowStyle">
        <Setter Property="FontFamily" Value="Tahoma" />
        <Setter Property="FontSize" Value="12" />
    </Style>
    
    <Style TargetType="ribbon:Ribbon">
        <Setter Property="FontFamily" Value="Tahoma" />
        <Setter Property="FontSize" Value="12" />
    </Style>

    <Style TargetType="TabControl">
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
    </Style>

    <Style TargetType="TabItem">
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
    </Style>
</ResourceDictionary>

سپس App.xaml را به این شکل تغییر دهید:

<Application x:Class="PrismApp.Shell.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/Generic.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

با این کار، Style های تعریف شده در کل برنامه در دسترس خواهند بود. اگر همه چیز مرتب باشد باید بتوانید پروژه را کامپایل کرده و فرم خالی برنامه را ببینید. در صورتی که با خطای زیر مواجه شدید، پوشه Debug پروژه را پاک کنید و مجددا برنامه را اجرا کنید:
An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
حالا باید سراغ module برویم.
روی solution راست کلیک کرده و پروژه جدیدی از نوع WPF User Control با نام PrismApp.Modules.MyFirstModule ایجاد کنید. فراموش نکنید module ما باید به کتابخانه های RibbonControlLibrary ،Prism ،Unity و Prism.UnityExtensions ارجاع داشته باشد. پس reference های لازم را به این شکل در Package Manager Console اضافه کنید:

PM> install-package Prism -ProjectName PrismApp.Modules.MyFirstModule
PM> install-package Unity -ProjectName PrismApp.Modules.MyFirstModule
PM> install-package Prism.UnityExtensions -ProjectName PrismApp.Modules.MyFirstModule

نگران دانلود مجدد آن ها نباشید. nuget ابتدا برسی می کند که آیا قبلا این package ها را دانلود کرده است یا نه، سپس اقدام به نصب آن ها می کند. RibbonControlLibrary از طریق nuget در دسترس نیست. reference آن را بطور دستی به پروژه  ی module اضافه کنید. همچنین فایل پیش فرض xaml داخل پروژه module را پاک کنید. دو پوشه به نام های Views و ViewModels بسازید. سپس در پوشه Views دو WPF User Control با نامهای MyModuleMainView.xaml و MyModuleRibbonTab.xaml بسازید. 
فایل MyModuleMainView.xaml را به این شکل تغییر دهید:

<TabItem x:Class="PrismApp.Modules.MyFirstModule.Views.MyModuleMainView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" Header="My First Module"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
            <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Message}"></TextBlock>            
    </Grid>
</TabItem>

از آنجایی که در Shell اصلی برنامه، یک TabControl میزبان View های ماست، پس بهتر است View های ماژول ما TabItem باشند. فراموش نکنید در فایل MyModuleMainView.xaml.cs نیز کلاس پدر را از UserControl به TabItem تغییر دهید. حالا همین کار را با MyModuleRibbonTab.xaml و فایل Code Behind مربوطه انجام می دهیم، با این تفاوت که این بار آن را به کلاس RibbonTab تبدیل می کنیم. فایل نهایی باید چیزی شبیه این باشد:

<ribbon:RibbonTab x:Class="PrismApp.Modules.MyFirstModule.Views.MyModuleRibbonTab"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns:commands="clr-namespace:PrismApp.Modules.MyFirstModule.Commands"
             Header="My Module"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
    <ribbon:RibbonGroup Header="My Module Items">
        <ribbon:RibbonButton Content="Save" Command="{x:Static commands:ModuleCommands.SaveCommand}"></ribbon:RibbonButton>
    </ribbon:RibbonGroup>
</ribbon:RibbonTab>
در این فایل یک ارجاع به فضای نامی ViewModels وجود دارد، همچنین در RibbonButton، مشخصه Command به یک مشخصه static در کلاس ModuleCommands متصل شده (bind) است. در ادامه این کلاس را ایجاد می کنیم:
پوشه ای به نام Commands بسازید و کلاس ModuleCommands را در آن ایجاد کنید:

using Microsoft.Practices.Prism.Commands;

namespace PrismApp.Modules.MyFirstModule.Commands
{
    public static class ModuleCommands
    {
        public static readonly CompositeCommand SaveCommand = new CompositeCommand();
    }
}

در این کلاس، فیلد عمومی به نام SaveCommand ایجاد کرده ایم تا از تمامی قسمت های ماژول به آن دسترسی داشته باشیم. نوع این فیلد CompositeCommand است که اجازه می دهد کلاس های ViewModel (یا هر کلاس دیگری) دیگر فرمان های مربوط به عمل Save خود را در آن ثبت کنند (Register) و با هر بار فراخوانی SaveCommand، تمامی Command های ثبت شده در آن اجرا (Execute) می شوند.
در پوشه ViewModels یک کلاس به نام MyModuleMainViewModel بسازید:

using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;

namespace PrismApp.Modules.MyFirstModule.ViewModels
{
    public class MyModuleMainViewModel : NotificationObject
    {
        private readonly INameService _textService;
        public MyModuleMainViewModel(INameService textService)
        {
                   _textService = textService;
            this.SaveCommand = new DelegateCommand(Save, CanSave);
             ModuleCommands.SaveCommand.RegisterCommand(SaveCommand);
        }


        private string _message = "Hello from My Module!";

        public string Message
        {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    RaisePropertyChanged(() => Message);
                }
            }
        }

        public DelegateCommand SaveCommand { get; private set; }

        public void Save()
        {
            this.Message =  string.Format("Save Command Executed for {0}.", _textService.GetRandomName());
        }

        public bool CanSave()
        {
            return true;
        }
    }
}

اگر با الگوی MVVM آشنا باشید می دانید که در ViewModel ها باید واسط INotifyPropertyChanged پیاده سازی شود. در Prism ما کلاس NotificationObject را پیاده سازی می کنیم. این کلاس علاوه بر پیاده سازی واسط مذکور، تابع کمکی برای فراخوانی رویداد PropertyChanged را نیز دارد. تابع RaisePropertyChanged پارامتری به شکل عبارت Lambda می پذیرد (علاوه بر حالت رشته ای) که احتمال اشتباه را در نوشتن نام Property ناممکن می کند. همچنین در صورت تغییر نام Property در تعریف، این ارجاع بطور اتوماتیک تغییر می کند. 
در این ViewModel ما یک Command از نوع کلاس DelegateCommand ایجاد کردیم. این کلاس Prism، یک پیاده سازی از واسط ICommand است. هنگام نمونه سازی از آن در تابع سازنده، دو تابع Save و CanSave را به آن ارجاع دادیم. سپس آن را در ModuleCommands.SaveCommand ثبت کردیم تا هنگامی که کاربر بر روی RibbonButton کلیک می کند، این Command اجرا شود. هنگام اجرای دستور، متنی به کاربر نمایش داده می شود که قسمتی از آن توسط سرویس INameService فراهم می شود. هدفم از قرار دادن این سرویس، آشنایی با نحوه پیاده سازی و استفاده از سرویس ها در برنامه های MVVM است.
سرویس INameService بسیار ساده است. پوشه ای به نام Services ایجاد کنید و یک واسط به نام INameService و یک کلاس به نام NameService در آن ایجاد کنید:

// INameService.cs file
namespace PrismApp.Modules.MyFirstModule.Services
{
    public interface INameService
    {
        string GetRandomName();
    }
}

// NameService.cs file
namespace PrismApp.Modules.MyFirstModule.Services
{
    using System;
    public class NameService : INameService
    {
        private readonly string[] _texts = { "Jalal", "Majid", "Saeed", "Alireza", "Esmaeil" };
        private readonly Random _random = new Random();

        public string GetRandomName()
        {
            // return random name...
            return _texts[_random.Next(0, _texts.Length)];
        }
    }
}


واسط INameService فقط اعلام می کند که قابلیت ارائه یک نام تصادفی را می پذیرد، کلاس NamedService نیز این قابلیت را برای آن پیاده سازی می کند.
در کل، هدف از ایجاد واسط ها و سپس کلاس های پیاده سازی کننده آن ها، جدا کردن جریان کار از نحوه پیاده سازی آن است. در اینجا، ViewModel ما فقط یک نام تصادفی می خواهد، و نیازی نیست بداند این نام تصادفی از کجا (آرایه؟ وب سرویس؟ بانک اطلاعاتی؟ ...) آمده است. بدین صورت شما ممکن است چندین پیاده سازی متفاوت از INameService داشته باشید و وابسته به شرایط و یا تنظیمات نرم افزار، از یکی از آنها را انتخاب کنید.
اکنون نوبت پیاده سازی کلاس MyFirstModule است. این کلاس نقطه اجرای ماژول ما است:


using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Unity;
using PrismApp.Modules.MyFirstModule.Services;
using PrismApp.Modules.MyFirstModule.ViewModels;
using PrismApp.Modules.MyFirstModule.Views;
using System;

namespace PrismApp.Modules.MyFirstModule
{
    public class MyFirstModule : IModule
    {
        private readonly IUnityContainer _container;
        private readonly IRegionManager _regionManager;

        public MyFirstModule(IUnityContainer container, IRegionManager regionManager)
        {
            _container = container;
            _regionManager = regionManager;
        }

        public void Initialize()
        {
            // Register Services
            _container.RegisterType<INameService, NameService>();

            // Register ViewModels
            _container.RegisterType<MyModuleMainViewModel>();

            //Register Views
            _container.RegisterType<Object, MyModuleMainView>();
            _container.RegisterType<Object, MyModuleRibbonTab>();

            // Register views inside named regions
            _regionManager.RegisterViewWithRegion("RibbonRegion", ResolveRibbonTabView);
            _regionManager.RegisterViewWithRegion("MainRegion", ResolveMainView);
        }

        private object ResolveRibbonTabView()
        {
            return _container.Resolve<MyModuleRibbonTab>();
        }

        private object ResolveMainView()
        {
            return _container.Resolve<MyModuleMainView>();
        }
    }
}

اگر به خاطر داشته باشید، در Bootstrapper، تابعی به نام CreateModuleCatalog وجود داشت که وظیفه آن بارگذاری ماژول ها بود. به تابع سازنده این کلاس دقت کنید. دو پارامتر IUnityContainer و IRegionManager، هر دو توسط Unity در کلاس Bootstrapper مقدار دهی شده و هنگام بارگذاری ماژول، به تابع سازنده آن تزریق می شوند.
در ادامه تابع Initialize توسط Bootstrapper فراخوانی می شود که شروع به ثبت سرویس ها، View و ViewModel ها در Unity می کند. در ادامه، View های مربوطه را در Region های از پیش مشخص شده (نام گذاری شده) ثبت می کند. بدین صورت (در این مثال) هنگام اجرای ماژول، View ها ایجاد شده و بر اساس نام مشخص شده، در Region مربوط به خود قرار می گیرند.
در صورتی که همه چیز مرتب باشد باید بتوانید ماژول خود را compile کنید. سپس به پوشه Bin\Debug آن رفته و assembly ماژول را (که PrismApp.Modules.MyFirstModule.dll نام دارد) در مسیر فایل اجرای برنامه (که PrismApp.Shell.exe) کپی نمایید. البته ویژوال استودیو قابلیت اتوماتیک کردن این روال را برای هر بار Build ماژول دارد. پس برای کار آسان شود، روی پروژه ماژول راست کلیک کرده و Properties را انتخاب کنید. زبانه Build Events را انتخاب کنید، سپس در کادر متنی Post-build events command line فرمان زیر را کپی کنید:
xcopy "$(TargetDir)*.*" "$(SolutionDir)PrismApp.Shell\bin\$(ConfigurationName)\" /Y
در صورتی که هنگام Compile ماژول به خطای زیر برخوردید، احتمالا این فرمان را (شاید چون نام پروژه شما متفاوت است) اشتباه نوشته اید.

سورس کد این قسمت را تحت عنوان Step3 از github دانلود کنید.

شنبه ۷ بهمن ۱۳۹۱ ه‍.ش.

آشنایی با Prism - Bootstrapper

در پست قبل اندکی با Bootstrapper، آشنا شدیم. اما این کلاس حرف های بسیاری برای گفتن دارد. در ادامه با برخی جزئیات این سلسله تنظیمات بیشتر آشنا می شویم.


شکل بالا گام های لازم برای مقدار دهی اولیه ی یک برنامه Prism را مشخص می کند. 

ایجاد یک ILoggerFacade: با override کردن تابع CreateLogger در Bootstrapper، شما این اختیار را دارید تا یک logger برای خود تهیه کنید. این logger در طول کل برنامه در دسترس شما خواهد بود. همچنین خود کتابخانه Prism، گزارش های خود را به این logger می فرستد تا شما در جریان روند امور باشید. برای طراحی یک logger کافیست یک کلاس تعریف کرده و واسط ILoggerFacade را در آن پیاده سازی کنید. ما برای سادگی کار گزارش ها را بر روی فایل ذخیره می کنیم. شما می توانید هر نوع پیاده سازی (ذخیره در بانک اطلاعاتی، ارسال ایمیل و ...) دلخواه خود را انجام دهید.
using System;
using System.IO;
using System.Text;
using Microsoft.Practices.Prism.Logging;

namespace PrismApp.Shell
{
    public class TextFileLogger : ILoggerFacade
    {
        private readonly string _fileName = Path.Combine(Environment.CurrentDirectory, "PrismApp.log");
        private readonly object _syncLock = new object();

        public TextFileLogger()
        {
        }

        public void Log(string message, Category category, Priority priority)
        {
            WriteToFile(category.ToString(), message);
        }


        private void WriteToFile(string type, string message)
        {
            lock (_syncLock)
            {
                using (var writer = new StreamWriter(_fileName, true, Encoding.UTF8))
                {
                    writer.WriteLine(string.Format("{0:G}\t{1}:{2}", DateTime.Now, type, message));
                }
            }
        }
    }
}

واسط ILoggerFacade فقط یک تابع Log دارد که در آن پیام، وضعیت و اولویت آن مشخص شده است. شما می توانید مطابق میل خود، اطلاعات لازم را دریافت و ذخیره کنید. در این مثال، فقط وضعیت و متن را ذخیره می شود. اکنون برای مقدار دهی اولیه logger در کلاس Bootstrapper به این شکل عمل می کنیم:

        protected override Microsoft.Practices.Prism.Logging.ILoggerFacade CreateLogger()
        {
            return new TextFileLogger();
        }
با ایجاد یک نمونه از کلاس ساخته شده و بازگرداندن آن، Prism، از این نمونه برای عملیات گزارش استفاده می کند.

ایجاد Module Catalog: در صورتی که برنامه ی شما ماژولار است، با پیاده سازی تابع CreateModuleCatalog می توانید نحوه بارگذاری ماژول های برنامه را مشخص کنید. برای مثال اگر می خواهید ماژول ها را از مسیر خاصی بارگذاری کنید می توانید یک نمونه از DirectoryModuleCatalog استفاده کنید.

        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new DirectoryModuleCatalog()
            {
                ModulePath = Environment.CurrentDirectory;
            };
        }

بدین وسیله، هنگام اجرای برنامه، Prism در مسیر مشخص شده (در این مثال، مسیر اجرای برنامه) به دنبال ماژول ها می گردد و در صورت پیدا کردن، آن ها را بارگذاری می کند. در پست های آینده با نحوه ایجاد ماژول بیشتر آشنا خواهیم شد.

پیکربندی Container: تزریق وابستگی نقش کلیدی را در برنامه های مبتنی بر Prism ایفا می کند. در این مرحله، سرویس های اصلی (Core Services)، بطور پیشفرض توسط Prism در Container ثبت می شوند. اما می توان این کار را بطور دستی نیز انجام داد. اضافه بر این، سرویس های برنامه کاربردی نیز باید توسط برنامه نویس در Container ثبت شوند. از انجایی که فعلا فقط قصد تکمیل اسکلت کامل یک برنامه ماژولار Prism را داریم، سرویس خاصی در Container ثبت نمی کنیم. Prism بطور پیش فرض، بسیاری از سرویس های مورد نیاز را ثبت می کند. در پست های بعدی به این قسمت برمیگردیم. فعلا تابع ConfigureContainer ما در حد ثبت MainWindow باقی می ماند.

        protected override void ConfigureContainer()
        {
            // Register default core services
            base.ConfigureContainer();
            // Register Shell (main window)
            Container.RegisterType<MainWindow>();
        }
پیکربندی RegionAdapterMapping و RegionBehavior پیش فرض: در برنامه های مبتنی بر Prism، مدیریت View ها اندکی متفاوت از برنامه های WPF عادی است. در یک برنامه ماژولار ممکن است View خاصی در یک ماژول تعریف شده باشد و با اجرای فرمانی نیاز باشد آن View به کاربر نمایش داده شود.  بدین منظور از قبل باید در View اصلی برنامه (همان Shell که در اینجا منظور MainWindow است) قسمت هایی تعریف شوند که قادر به نمایش View ها باشند. به این قسمت ها Region گفته می شود. بدین صورت شما می توانید در Shell برنامه، یک یا چند Region با نام های منحصر به فرد طراحی کنید تا ماژول ها بتوانند در آن View های خود را نمایش دهند. در پست های بعدی بیشتر به این قسمت خواهیم پرداخت و فعلا توابع مربوط به آن را override نمی کنیم.

ثبت FrameworkExceptions: شما می توانید استثنا (Exception) های خاصی را در Prism ثبت کنید تا هنگام رخ دادن، استثنای اصلی توسط تابع GetRootException پیدا شده و نمایش داده شود. استثناهای ثبت شده، به سادگی در یک لیست Static ذخیره می شوند. در صورتی که یک Exception، در این لیست ثبت نشده باشد، پس از رخ دادن بدون جستجو برای InnerException اصلی، دوباره throw می شود. از این امکان بسیار به ندرت استفاده می شود.

CreateShell: همانطور که در پست قبل خواندید، از این تابع برای ایجاد View اصلی برنامه (که Shell نامیده می شود) استفاده می شود. در ادامه تابع دیگری به نام InitializeShell وجود دارد که پس از این تابع فراخوانی می شود و پیاده سازی آن نیز اختیاری است. در صورتی که برنامه شما WPF باشد می توانید در این تابع، کد مربوط به مقدار دهی اولیه و نمایش پنجره اصلی را بنویسید. بدین جهت، تغییرات زیر در کلاس Bootstrapper انجام می شود:

        protected override System.Windows.DependencyObject CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void InitializeShell()
        {
            Application.Current.MainWindow = (Window)this.Shell;
            Application.Current.MainWindow.Show();
        }
بدین صورت، ما با override کردن تابع CreateShell، فقط یک نمونه سازی از کلاس MainWindow انجام می دهیم و سپس در تابع InitializeShell، پنجره اصلی برنامه WPF را مشخص کرده و آن را به کاربر نمایش می دهیم.

InitializeModules: همان طور که قبلا ذکر شد، در تابع ConfigureContainer، برخی سرویس های اصلی Prism با کلاس های پیش فرض ثبت می شوند. یکی از آن سرویس ها، IModuleManager است. این سرویس (واسط) وظیفه بارگذاری ماژول های برنامه را بر عهده دارد. در این سرویس، رویداد هایی به نام ModuleDownloadProgressChanged و LoadModuleCompleted وجود دارد که با subscribe شدن، می توان پیشرفت بارگذاری ماژول ها و زمان اتمام بارگذاری آن ها را مشاهده کرد. کتابخانه Prism یک پیاده سازی پیشفرض (کلاس) از این سرویس به عنوان ModuleManager دارد که در تابع ConfigureContainer، آن را ثبت کرده است. در صورتی که پس از override کردن این تابع، تابع base آن فراخوانی شده باشد، این سرویس بطور پیش فرض در Container ثبت شده است و نیازی نیست چیزی را تغییر بدهید.

خب، تا به این جا به اندازه کافی با Bootstrapper آشنا شده ایم. در پست بعدی به سراغ ماژول نویسی رفته و بخشی از مفاهیم Region ها را در کنار آن می آموزیم.

دریافت سورس کد کامل با تگ Step 2

پنجشنبه ۵ بهمن ۱۳۹۱ ه‍.ش.

آشنایی با Prism - شروع به کار

Prism یک فریم ورک برای طراحی نرم افزارهای قوی، قابل انعطاف با نگه داری آسان در WPF، Silverlight و Windows Phone است.
به کمک Prism شما می توانید برنامه هایی با اجزای وابسته ضعیف طراحی کنید که می توانند مستقل باشند ولی به آسانی به برنامه اصلی مجتمع شوند. این گونه برنامه ها عموما برنامه های ترکیبی (Composite Application) گفته می شود.

برای شروع می توانید Prism را به همراه سورس آن دانلود کرده و بر روی سیستم خود extract کنید. کتابخانه Prism برای برنامه های Desktop (و WPF) در مسیر bin\Desktop قرار دارد. 
Prism جهت تزریق وابستگی ها، بطور پیشفرض با Unity کار می کند. ولی در صورتی که با فریم ورک های دیگر تزریق وابستگی کار می کنید، دست شما باز است. مثلا برای Ninject یک extension ویژه استفاده در Prism تهیه شده است. همینطور Structure Map هم دارای کلاس های ویژه Prism است.

در صورتی که با مفاهیم تزریق وابستگی آشنایی ندارید، می توانید از اینجا شروع کنید.

ابزار کار لازم
  • Visual Studio نسخه 2010 یا 2012 (البته نسخه های قدیمی تر هم قابل استفاده هستند ولی نسخه Prism 4.1 فقط با دات نت 4 کار می کند. در صورتی که از Visual Studio 2008 استفاده می کنید باید نسخه Prism 2.1 را نصب کنید.)
  • اگر تمایلی به دستکاری source code کتابخانه Prism ندارید می توانید براحتی آن را توسط Nuget نصب کنید. پس درصورتی که آن را ندارید، دانلود کرده و نصب کنید.


آماده سازی محیط کار
ویژوال استودیو را اجرا کنید و یک پروژه جدید از نوع WPF Application ایجاد کنید. دقت کنید، دات نت 4 در لیست Target Framework انتخاب شده باشد. حالا در پنجره Package Manager Console دستور زیر را وارد کنید:

Install-Package Prism

و در ادامه برای نصب Unity، دستور زیر را وارد کنید:

Install-Package Unity

حالا نوبت نصب Unity Extension برای Prism است:

Install-Package Prism.UnityExtensions

برنامه های WPF از فایلی به نام App.xaml شروع می شوند. بطور پیش فرض، در این فایل، مسیر پنجره اصلی برنامه در یک مشخصه به نام StartupUri تعیین شده است ولی برنامه های مبتنی بر Prism، راه حلی متفاوت برای شروع دارند.

کلاس Bootstrapper
Bootstrapper نام کلاسی است که تمامی پیکربندی اولیه شروع برنامه در آن مشخص می شود. در Solution Explorer بر روی پروژه راست کلیک کرده و کلاس جدید به آن اضافه کنید. آن را Bootstrapper بنامید و کد زیر را در آن بنویسید.

    public class Bootstrapper : UnityBootstrapper
    {
        protected override System.Windows.DependencyObject CreateShell()
        {
            var mainView = new MainWindow();
            mainView.Show();
            return mainView;
        }
    }

کلاس Bootstrapper ما از کلاس UnityBootstrapper ارث برده است. تنها تابع که پیاده سازی آن اجباری است، تابع CreateShell است. این تابع، مقداری از نوع DependencyObject برمیگرداند که نقطه شروع نمایش برنامه را مشخص می کند. از آنجایی که برنامه ما یک WPF Application است، و در آن، MainWindow.xaml قرار است فرم اصلی باشد، ما یک نمونه از ان را ایجاد کرده، نمایش داده و برمی گردانیم. البته این روش new کردن به صورت دستی چندان مناسب نیست، بهتر است به کمک Unity این نمونه سازی را انجام دهیم:

    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Prism.UnityExtensions;

    public class Bootstrapper : UnityBootstrapper
    {
        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();

            Container.RegisterType<MainWindow>();
        }

        protected override System.Windows.DependencyObject CreateShell()
        {
            var mainView = Container.Resolve<MainWindow>();
            mainView.Show();
            return mainView;
        }
    }

اکنون یک تابع دیگر از UnityBootstrapper را override کرده ایم و در آن علاوه بر فراخوانی ConfigureContainer کلاس والد، پنجره اصلی (کلاس MainWindow) را در Unity Container ثبت کردیم. از تابع ConfigureContainer جهت رجیستر کردن سرویس ها، اینترفیس ها و کلاس های مورد نیاز در برنامه استفاده می شود. (using ها فراموش نشود!)
به App.xaml برمیگردیم. ابتدا مشخصه StartupUri و مقدار آن را پاک کرده و سپس به code behind آن رفته و تغییرات زیر را ایجاد کنید:

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            var bootstrapper = new Bootstrapper();
            bootstrapper.Run();

        }
    }
ما تابع OnStartup را override کردیم. این تابع با شروع برنامه، اجرا می شود. در آن یک نمونه از Bootstrapper ساختیم و تابع Run آن را اجرا کردیم.
اکنون پروژه را کامپایل نموده و اجرا کنید. در صورتی که تمام مراحل را بدون خطا سپری کرده باشید، پنجره برنامه را مشاهده خواهید کرد.

سورس کامل این قسمت را تحت عنوان Step 1 از اینجا دانلود کنید.

یکشنبه ۱ بهمن ۱۳۹۱ ه‍.ش.

آیا الگوی Service Locator یک Anti-Pattern است؟


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

الگوی Service Locator یک الگوی طراحی در توسعه نرم افزار است که پروسه های اختصاص دادن یک سرویس را به کمک یک لایه انتزاعی کپسوله سازی (مخفی) می کند. این الگو از یک رجیستری مرکزی (مرکز ثبت) که با عنوان Service Locator شناخته می شود، استفاده می کند، که با دریافت یک درخواست اطلاعات لازم برای انجام یک عملیات مشخص را برمی گرداند.

به زبان ساده تر، ما یک مرکز ثبت داریم که سرویس ها رو در اونجا ثبت (register) می کنیم و هنگام نیاز، اون سرویس رو فراخوانی (resolve) می کنیم. اگه با IoC Container ها (واگذاری مسئولیت) آشنا باشید این الگو رو راحت تر درک می کنید. درواقع خود همین IoC Container ها یک جور Service Locator پیشرفته اند. اگر با IoC Container ها آشنا نیستید این مقاله آقای وحید نصیری رو مطالعه کنید.

خب، بذارید با کد براتون توضیح بدم. این کد، یک Service Locator پیشنهادی ساده ست که در وبلاگ آقای Mark Seemann ارائه شده:

public static class Locator
{
    private readonly static Dictionary<Type, Func<object>>
        services = new Dictionary<Type, Func<object>>();
 
    public static void Register<T>(Func<T> resolver)
    {
        Locator.services[typeof(T)] = () => resolver();
    }
 
    public static T Resolve<T>()
    {
        return (T)Locator.services[typeof(T)]();
    }
 
    public static void Reset()
    {
        Locator.services.Clear();
    }
}

در این Service Locator، سرویس ها یک دیکشنری ذخیره میشن که به ازای هر نوع، فقط یک تابع برای ایجاد سرویس ذخیره می شه. یک نمونه استفاده از این Service Locator:

public interface ISort
{
     void Sort(IList<int> list);
}

public class MergeSort : ISort
{
      public void Sort(IList<int> list)
      {
          // merge sort algorithm goes here...
      }
}

// 1-initialize
Locator.Register<ISort>(() => new MergeSort());
.
.
.
List<int> numbers = new List<int>() { 6, 2, 5, 8, 1, 3 };
// 2-use
ISort sortAlgorithm = Locator.Resolve<ISort>();
sortAlgorithm.Sort(numbers)


ما یک سرویس به اسم ISort داریم که ازش یک پیاده سازی به نام MergeSort موجوده. در ابتدای برنامه (یا هرجایی، قبل از استفاده از سرویس) سرویس رو در Service Locator ثبت می کنیم (گام اول) و بعد هنگام استفاده ابتدا اون رو Resolve می کنیم (گام دوم).

خب حالا به این موضوع بپردازیم که چرا این الگو یک Anti-Pattern به حساب می آد؟


به نظر آقای Mark، این الگو یک Anti-Pattern به حساب میاد و با ذکر یک مثال این موضوع رو بیان می کنه:

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

فرض کنید IOrderValidator و IOrderShipper از قبل در Service Locator ما ثبت شده باشند. بنابراین تکه کد بالا باید بدون مشکل اجرا بشه و این اتفاق می افته. ولی چه مشکلی ممکنه پیش بیاد که اجرای این کد به خطا بخوره؟

آقای Mark در دو زمینه این موضوع رو برسی می کنه.



مشکل هنگام استفاده از OrderProcessor بعنوان یک API:

فرض کنید ما صرفا استفاده کننده از OrderProcessor هستیم و به سورس کد اون هیچ دسترسی ای نداریم. منظورم اینه که این کلاس توی یک فایل dll قرار گرفته باشه و ما از نحوه کارش بی خبریم. هنگام کد نویسی، چی ازش می بینیم؟
همونطور که توی تصویر می بینید، این کلاس یک تابع سازنده بدون پارامتر داره، به این معنی که راحت می تونید یک نمونه از اون بسازید و ازش استفاده کنید.
var order = new Order();
//
// fill order
// ...
var orderProcessor = new OrderProcessor();
orderProcessor.Process(order);

متاسفانه هنگام استفاده از این کلاس، متوجه وابستگی اون به سرویس های دیگه ای مثل IOrderValidator و IOrderShipper نمی شیم و مسلم هست که (در خط آخر) به خطای KeyNotFoundException می خوریم. چون این دوتا سرویس توی Locator ثبت نشدن. یا باید مستندات استفاده از اون رو کامل می خوندیم، یا با یک reflector، به کد اولیه دسترسی پیدا می کردیم تا از نحوه کارش با خبر بشیم. تازه یک مسئله رنج آور دیگه اینه که اگه بخواهیم از این Locator توی آزمون های واحد (unit test) استفاده کنیم هر بار باید تابع Locator.Reset رو فراخوانی کنیم. چون سرویس ها بطور static ذخیره شدند.


مشکل در نگهداری:

خب حالا بعد از یک مدت باید یک امکان جدید به این کد اضافه بشه. مثلا لازم شده IOrderCollector.Collect، اون بین فراخوانی بشه. تابه Process به این شکل تغییر می کنه:
public void Process(Order order)
{
    var validator = Locator.Resolve<IOrderValidator>();
    if (validator.Validate(order))
    {
        var collector = Locator.Resolve<IOrderCollector>();
        collector.Collect(order);
        var shipper = Locator.Resolve<IOrderShipper>();
        shipper.Ship(order);
    }
}

خب. حالا چی؟ ممکنه خوش شانس باشیم و IOrderCollector قبلا در یک قسمت دیگه یا در ابتدای unit test توی Locator ثبت کرده باشیم. بنابراین همه ی آزمون های ما (بطور اتفاقی) با موفقیت پاس می شوند. اما اگه توی کد محصول نهایی (Production Code) اینقدر خوش شانس نباشیم چی؟ برنامه هایی که از نسخه جدید کلاس OrderProcessor استفاده می کنند ممکنه با خطای شکننده (Breaking Changes) مواجه بشن.

در نهایت آقای Mark راه حل استفاده از Abstract Service Locator (بطور انتزاعی) رو مطرح می کنند که البته از نظر ایشون چنگی به دل نمی زنه! منظور ایشون اینه که بیایم یک واسط از Service Locator طراحی کنیم و اون رو به عنوان وابستگی کلاس OrderProcessor معرفی کنیم. یه همچین چیزی:

public interface IServiceLocator
{
    T Resolve<T>();
}
 
public class Locator : IServiceLocator
{
    private readonly Dictionary<Type, Func<object>> services;
 
    public Locator()
    {
        this.services = new Dictionary<Type, Func<object>>();
    }
 
    public void Register<T>(Func<T> resolver)
    {
        this.services[typeof(T)] = () => resolver();
    }
 
    public T Resolve<T>()
    {
        return (T)this.services[typeof(T)]();
    }
}

خب این IServiceLocator دقیقا همون چیزی هست که قبلا مایکروسافت توی دات نت گنجونده. System.IServiceProvider برای کارهای همه منظوره. در نهایت، کلاس OrderProcessor رو به این شکل تغییر میدیم:
public class OrderProcessor : IOrderProcessor
{
    private readonly IServiceLocator locator;
 
    public OrderProcessor(IServiceLocator locator)
    {
        if (locator == null)
        {
            throw new ArgumentNullException("locator");
        }
 
        this.locator = locator;
    }
 
    public void Process(Order order)
    {
        var validator = 
            this.locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper =
                this.locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

حالا کلاس OrderProcessor، توی تابع سازندش، به یک نسخه از IServiceLocator احتیاج داره. بدین صورت کلاس OrderProcessor رو به IServiceLocator وابسته می کنیم تا برنامه نویس قبل از استفاده متوجه بشه که این کلاس، یک وابستگی داره. 

خب؟ این به ما چی میگه؟ تقریبا چیز خاصی عوض نشده. فقط به زبان بی زبانی به کاربر گفته می شه که توی locator باید بعضی سرویس ها (کدوم سرویس ها؟) ثبت شده باشند. بدون اینکه اسمی از سرویس ها برده بشه. خلاصه آقای Mark و همفکرهاش قانع نشدند. بزرگترین بهانه اون ها اینه که چرا رخ دادن خطاهای compile time به runtime  موکول شده؟ جالبه بسیاری از کامنت های پست ایشون، نظرات متفاوتی رو مطرح کردند. 

اما آیا Service Locator یک Pattern خوب به حساب میاد یا یک Anti-Pattern؟ یا شاید از این الگو به درستی (برای مسئله مناسب) استفاده نشده؟ نظر شما چیه؟