جمعه ۷ فروردین ۱۳۹۴ ه‍.ش.

پیاده سازی الگوی NotifyPropertyChanged به کمک Attribute در Unity DI

پیشنیاز این مطلب، آشنایی با الگوی طراحی MVVM و آشنایی با مفاهیم تزریق وابستگی به کمک Unity است.

اگر از طرفداران الگوی طراحی MVVM در توسعه برنامه های مبتنی بر WPF باشید حتما مجبور شده اید برای کلاس های View Mode خود واسط INotifyPropertyChanged را پیاده سازی کنید. یکی از راه های معمول و ساده، پیاده سازی کلاسی پایه و پیاده سازی تابعی برای فراخوانی رویداد ProperyChanged است که نمونه ی آن را در زیر می بینید:

public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

public class PersonViewModel : BaseViewModel
{
private string _fullName;
public string FullName
{
get
return _fullName; 
}
set
{
if(_fullName != value)
{
_fullName = value;
OnPropertyChanged("FullName");
}
}
}
}

ولی بهتر نمی شد اگر کلاس PersonViewModel ما به این شکل می شد؟:

public class MainViewModel : BaseViewModel
{
public string FullName { get; [Notify]set; }
}
و کلاس BaseViewModel به این شکل:

public class BaseViewModel : MarshalByRefObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}

اگر با من موافق هستید دست به کار شوید. Visual Studio را باز کنید و یک پروژه MS Unit Test بسازید. سپس در Nuget Package Manager Console دستور زیر را وارد کنید تا همه آنچه لازم داریم را نصب کنیم:

Install-Package Unity.Interception

این دستور بسته Unity.Interception را نصب می کند. از آنجایی که این بسته نیازمند Unity است پس بسته Unity هم نصب می شود.
ابتدا کلاس BaseViewModel.cs را به شکل زیر اضافه کنید:

public class BaseViewModel : MarshalByRefObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
دقت کنید این کلاس حتما باید از MarshalByRefObject ارث بری کند. کاربرد اصلی این کلاس در Remoting است. هنگامی که یک Process درخواست فراخوانی تابعی از یک شیء در Process دیگر را دارد، در صورتی که آن نوع آن شیء از MarshalByRefObject ارث بری کرده باشد، به جای ارسال کپی این شیء، یک Proxy ایجاد شده و به Process فراخوان ارسال می شود. در ادامه متوجه خواهیم شد چرا به Proxy نیاز است.

اکنون نوبت به پیاده سازی ICallHandler می رسد. کلاسی با نام NotifyPropertyChangedCallHandler بسازید و کد زیر را در آن قرار دهید:

public class NotifyPropertyChangedCallHandler : ICallHandler
{
private readonly string PropertyName = null;
public NotifyPropertyChangedCallHandler(string propertyName, int order)
{
this.PropertyName = propertyName;
this.Order = order;
}

public NotifyPropertyChangedCallHandler()
: this(null, 0)
{
}

#region ICallHandler Members

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
bool shouldRaise = ShouldRaiseEvent(input);
IMethodReturn result = getNext()(input, getNext);

if (result.Exception == null && shouldRaise)
{
if (String.IsNullOrWhiteSpace(PropertyName))
RaiseEvent(input);
else
RaiseEvent(PropertyName, input.MethodBase.DeclaringType, input.Target);
}

return result;
}

private bool ShouldRaiseEvent(IMethodInvocation input)
{
MethodBase methodBase = input.MethodBase;

//Is the method a property setter?
if (!methodBase.IsSpecialName || !methodBase.Name.StartsWith("set_"))
{
return false;
}

//Get the name of the property out so we can use it to raise a 
//property changed event
string propertyName = this.PropertyName ?? methodBase.Name.Substring(4);

//Retrieve the property getter
PropertyInfo property = methodBase.ReflectedType.GetProperty(propertyName);
MethodInfo getMethod = property.GetGetMethod();

//IF the property has no get method, we don't care
if (getMethod == null)
{
return false;
}

//Get the current value out
object oldValue = getMethod.Invoke(input.Target, null);

//Get the updated value
object value = input.Arguments[0];

//Is the new value null?
if (value != null)
{
//Is the new value different from the old value?
if (value.Equals(oldValue) == false)
{
return true;
}
}
else
{
//Is the new value (null) different from the 
//old value (non-null)?
if (value != oldValue)
{
return true;
}
}

return false;
}

private void RaiseEvent(IMethodInvocation input)
{
FieldInfo field = null;

//Get a reference to the PropertyChanged event out of the current 
//type or one of the base types
var type = input.MethodBase.ReflectedType;
while (field == null && type != null)
{
field = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
type = type.BaseType;
}

//If we found the PropertyChanged event
if (field != null)
{
//Get the event handler if there is one
var evt = field.GetValue(input.Target) as MulticastDelegate;
if (evt != null)
{
//Get the property name out
string propertyName = input.MethodBase.Name.Substring(4);
//Invoke the property changed event handlers
evt.DynamicInvoke(input.Target, new PropertyChangedEventArgs(propertyName));
}
}
}

private void RaiseEvent(string propertyName, Type targetType, object targetInstance)
{
FieldInfo field = null;

//Get a reference to the PropertyChanged event out of the current 
//type or one of the base types
var type = targetType;
while (field == null && type != null)
{
field = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
type = type.BaseType;
}

//If we found the PropertyChanged event
if (field != null)
{
//Get the event handler if there is one
var evt = field.GetValue(targetInstance) as MulticastDelegate;
if (evt != null)
{
//Invoke the property changed event handlers
evt.DynamicInvoke(targetInstance, new PropertyChangedEventArgs(propertyName));
}
}
}

public int Order
{
get;
set;
}

#endregion
}

مهمترین تابع این کلاس Invoke است که توسط Unity فراخوانی می شود. پارامتر input از نوع InvocationMethod تابعی از کد کاربر که قرار است فراخوانی شود را مشخص می کند. در مثال ما این تابع set_FullName خواهد بود. چرا؟ چون تابع set از مشخصه (Property) با نام FullName کلاس PersonViewModel را به NotifyAttribute مزین کرده ایم. در ادامه درباره NotifyAttribute خواهیم خواند. تا اینجا متوجه شده اید که قرار است تغییراتی در تابع set_FullName اتفاق بیوفتد. اجازه بدهید کد این تابع را مرور کنیم. در ابتدا تابع ShouldRaiseEvent فراخوانی می شود. اگر این تابع را برسی کنید متوجه می شوید ابتدا چک می کند که آیا واقعا تابع مورد نظر یک set هست یا خیر. در ادامه برسی می کند که آیا مقدار ارسالی به این مشخصه (Property) با مقدار موجود در آن متفاوت است یا خیر. دقیقا همان کاری که در حالت کلاسیک چک می کردیم. در صورتی که مقادیر متفاوت باشد مقدار true و در غیر این صورت مقدار false را برمی گرداند. بنابراین همانطور که از نامش مشخص بود می فهمیم آیا باید رویداد PropertyChanged را فراخوانی کنیم یا خیر. در ادامه خود تابع set فراخوانی خواهد شد:
IMethodReturn result = getNext()(input, getNext);

 یعنی همان عمل عادی قرار دادن value در مشخصه FullName. ولی چرا به این شکل فراخوانی شده؟ به کمک getNext در واقع Unity به دنبال یک ICallHandler احتمالی دیگر می رود و در صورت وجود آن را اجرا می کند. از انجایی که در مثال ما فقط یک ICallHandler وجود دارد (همان NotifyPropertyChangedCallHandler) پس بعد از آن خود تابع set اجرا می شود و مقدار جدید (فارغ از تکراری بودن یا نبودن) در مشخصه FullName قرار می گیرد.
در ادامه اگر خطایی رخ نداده بود، با برسی shouldRaise رویداد PropertyChanged را فراخوانی می کنیم. دقت کنید که دو نوع تابع RaiseEvent وجود دارد که نوع اول آن، رویداد PropertyChanged را با نام مشخصه ای که از پارامتر input گرفته فراخوانی می کند و نوع دوم از نامی که در تابع سازنده مشخص شده استفاده می کند.

حالا باید NotifyAttribute را تعریف کنیم. از این کلاس برای مزین کردن مشخصه هایی که می خواهیم PropertyChanged برای آن ها فراخوانی شود استفاده می کنیم. کد زیر را در این کلاس وارد کنید:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]
public class NotifyAttribute : HandlerAttribute
{
private readonly ICallHandler handler;
public string PropertyName { get; set; }
public NotifyAttribute()
{
}

public NotifyAttribute(string propertyName)
{
this.PropertyName = propertyName;
}

public NotifyAttribute(string propertyName, int order)
{
this.PropertyName = propertyName;
this.Order = order;
}

public override ICallHandler CreateHandler(IUnityContainer container)
{
if (string.IsNullOrWhiteSpace(PropertyName))
return new NotifyPropertyChangedCallHandler();
return new NotifyPropertyChangedCallHandler(PropertyName, Order);
}
}

چیز زیادی برای توضیح نیست. نام مشخصه مورد نظر توسط propertyName و ترتیب اجرا توسط order مشخص می شود. در صورتی که این پارامترها را وارد نکنیم، propertyName توسط پارامتر input در تابع Invoke بدست می آید و order نیز به طور پیش فرض مقدار 0 خواهد داشت. با اجرای CreateHandler توسط Unity در واقع ICallHandler مورد نظر را برای Unity مشخص می کنیم.

حالا به سراغ کلاس PersonViewModel بروید:

public class PersonViewModel : BaseViewModel
{
public string FullName { get; [Notify]set; }
public DateTime Birthday { get; [Notify(Order = 1)][Notify(PropertyName = "Age", Order = 2)]set; }
public int Age { get { return DateTime.Now.Year - Birthday.Year; } }
}

به مشخصه Birthday دقت کنید. با set شدن مقدار جدید به آن رویداد PropertyChanged برای مشخصه Age هم فراخوانی می شود. اکنون متوجه می شوید چرا در کلاس NotifyAttribute چند تابع سازنده داشتیم یا چرا در کلاس NotifyPropertyChangedCallHandler دو تابع RaiseEvent داشتیم. اکنون می توانیم با فرستادن نام و ترتیب اجرای مشخصه مورد نظر، نحوه فراخوانی رویداد PropertyChanged را کنترل کنیم.
اکنون وقت خروجی گرفتن است. کد زیر را در UnitTest بنویسید:

[TestClass]
public class PropertyChangesUnitTest
{
[TestMethod]
public void CheckPropertyChangedGetsCalledForPropertiesInAttributeWay()
{
using (var container = new UnityContainer())
{
container.AddNewExtension<Interception>();
container.RegisterType<PersonViewModel>(
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>());

var propertyChanged = false;
var propertyNames = new List<string>();
var viewModel = container.Resolve<PersonViewModel>();

viewModel.PropertyChanged += (sender, e) =>
{
propertyChanged = true;
propertyNames.Add(e.PropertyName);
};

viewModel.FullName = "new value!";
Assert.IsTrue(propertyChanged, "PropertyChanged not called!");
Assert.IsTrue(propertyNames.Contains("FullName"));
propertyNames.Clear();

viewModel.Birthday = new DateTime(1990, 1, 1);
Assert.IsTrue(propertyChanged, "PropertyChanged not called!");
Assert.IsTrue(propertyNames.Contains("Birthday"));
Assert.IsTrue(propertyNames.Contains("Age"));
}
}
}

پس از نمونه سازی از UnityContainer، افزونه Interception را به آن اضافه می کنیم. درواقع تمام این عملیات به کمک Interception ها انجام می شود. به کمک Interception می توان کد کاربر را احاطه کرد و از بیرون بر روی نحوه اجرای قسمت های مختلف آن (همانند مثال ما) اعمال کنترل نمود. در ادامه هنگام ثبت کلاس PersonViewModel از دو کلاس TransparentProxyInterceptor و رفتار PolicyInjectionBehavior استفاده می کنیم. به کمک TransparentProxyInterceptor مشخص می کنیم که عملیات احاطه کردن کد (Interception) باید به کمک ساخت Proxy از کلاس کاربر انجام شود. حالا می توان دلیل استفاده از MarshalByRefObject را متوجه شد. در ادامه توسط PolicyInjectionBehavior مشخص می کنیم که Policy های ما به کمک attribute هایی در کلاس ثبت شده (در اینجا PersonViewModel) اعمال شده اند.

برای آشنایی بیشتر با Unity و Interception ها از اینجا شروع کنید.
سورس کد را از این repository دریافت کنید.

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

خطای عجیب 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 دانلود کنید.