Introduction
There are quite a few articles written about Prism 4 in CodeProject. Many of you have done a very good job and have explained the mechanics very well. I have decided to take Prism 4 to a different point of view using MEF (Managed Extensibility Framework) and this does not compare with the other articles.
The article provides you with a framework to navigate Views using Menu Bars and Ribbon control. It builds on MVVM pattern (Model View View-Model) that loose coupling Shell application and Modules. Prism using MEF is very a different approach compared to Unity and it may be hard to switch from one to another.
Background
This article is focused on Prism 4 using MEF (Managed Extensibility Framework) instead of Unity. First we go through the similarities between MEF and Unity. MEF registers types and instances using IoC concepts and Unity registers types and instances with the container. They imperatively create instances of registered types. They inject instances of registered types into constructors and properties. They have declarative attributes for marking types and dependencies that need to be managed. Both of them resolve dependencies in an object graph.
However, Unity has several capabilities that are not found in MEF; as Unity resolves concrete types without registration and resolves open generics. Unity uses interception to capture calls to objects and add additional functionality to the target object.
Only MEF discovers assemblies in a directory and uses XAP file download and assembly discovery that is advantageous in SilverLight Application. It recomposes properties and collections as new and automatically exports derived types.
This article assumes that you have gone through the articles in CodeProject, Creating View-Switching Applications with Prism and A Prism 4 Application Checklist. The articles explained very well about the Shell Windows, Bootstrapper, Modules and Views. Please read them first, if you have not as this article does not cover them again to avoid duplication.
MVVM (Model View ViewModel)
MVVM pattern is trying to separate the functionalities between the Model
and View
. The ViewModel
acts as Business Object inherent data checking to validate the incoming data and executes functionalities through Data Binding. Prism using MEF makes it easier to design the framework for the MVVM pattern by Import
and Export
objects between Views
, ViewModel
and Model
. The demo project used Module Controller class to reduce the duplication of functionalities and it may have been a debate whether to use a controller or not. However, if it makes things easier for me, I am always for it rather than a die-hard pattern follower.
Prism Libraries
Prism MEF references are slightly different from Unity:
Add references to the following Prism assemblies to your new module project:
- Microsoft.Practices.Prism.dll
- Microsoft.Practices.ServiceLocation.dll
- Microsoft.Practices.Prism.MefExtensions.dll
You will need to refer to System.ComponentModel.Composition.dll for the [Import]
and [Export()]
attributes.
The Demo Application
The demo application has four projects as follows:
- Prism4MefDemo.exe creates from WPF Ribbon Application’s template, make sure you have installed Microsoft Ribbon for WPF 4.0.
- Prism4MefDemo.Infrastructure.dll is the main support class library.
- Prism4MefDemo.ModuleOne.dll is first Module based on the Prism guidance.
- Prism4MefDemo.ModuleTwo.dll is just another Module.
The Shell Demo Application has three Regions in the Shell Window as follows:
RibbonRegion
– Hosting the Ribbon
Menu
NavigatorRegion
– Hosting the Menubar
similar to Outlook Menubar built from Listbox
control
WorkspaceRegion
- Hosting views for input and output of the application
The ShellWindow
class has an [Export]
attribute that has a reference ExportAttribute
class in System.ComponentModel.Composition.dll that exports infer type and contract name.
The Prism4MefBootstapper
class uses MefBootstrapper
inheritance rather than UnityBootstrapper
and has a MefBootstrapper.Container
. The override DependencyObject CreateShell()
is different from Prism Unity using MefBootstrapper.Container
to get export value of the ShellWindow
as follows:
protected override DependencyObject CreateShell()
{
return this.Container.GetExportedValue<shellwindow>();
}
In order to initialise Prism4MefBootstaper
class, we need to add new
to the AssemblyCatalog
in the override ConfigureAggregateCatalog
.
protected override void ConfigureAggregateCatalog()
{
this.AggregateCatalog.Catalogs.Add
(new AssemblyCatalog(typeof(Prism4MefBootstapper).Assembly));
}
The demo application has auto populate views using three classes in the Prism4MefDemo.Infrastructure
as follows:
- The
AutoPopulateExportedViewsBehavior
class inherits Microsoft.Practices.Prism.Regions.RegionBehavior
class and interfaces System.ComponentModel.Composition
. IPartImportsSatisfiedNotification
.
- The main method of the
AutoPopulateExportedViewsBehavior
class is the AddRegisteredViews
that overrides the RegionBehavior.OnAttach
and implements the IPartImportsSatisfiedNotification.OnImportsSatisfied
. It has a property RegisteredViews
to keep the View Region Registration.
- The
ViewExportAttribute
class inherits System.ComponentModel.Composition.ExportAttribute
and interfaces IViewRegionRegistration
that will implement RegionName
property.
The IViewRegionRegistration
interface has RegionName
property. Once again, we add new AutoPopulateExportedViewsBehavior
class in the override ConfigureAggregateCatalog
as below:
protected override void ConfigureAggregateCatalog()
{
this.AggregateCatalog.Catalogs.Add
(new AssemblyCatalog(typeof(AutoPopulateExportedViewsBehavior).Assembly));
}
Once we have done that, we can override ConfigureDefaultRegionBehaviors
as below:
protected override Microsoft.Practices.Prism.Regions.
IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
var factory = base.ConfigureDefaultRegionBehaviors();
factory.AddIfMissing("AutoPopulateExportedViewsBehavior",
typeof(AutoPopulateExportedViewsBehavior));
return factory;
}
The overrides CreateModuleCatalog
to load the modules using the App.config are as follows:
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}
="1.0" ="utf-8"
<configuration>
<configSections>
<section name="modules"
type="Microsoft.Practices.Prism.Modularity.ModulesConfigurationSection,
Microsoft.Practices.Prism"/>
</configSections>
<modules>
<module assemblyFile="Prism4MefDemo.ModuleOne.dll"
moduleType="Prism4MefDemo.ModuleOne.ModuleOne, Prism4MefDemo.ModuleOne.ModuleOne,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
moduleName="ModuleOne" startupLoaded="true" />
<module assemblyFile="Prism4MefDemo.ModuleTwo.dll"
moduleType="Prism4MefDemo.ModuleTwo.ModuleTwo, Prism4MefDemo.ModuleOne.ModuleTwo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
moduleName="ModuleTwo" startupLoaded="true" />
</modules>
</configuration>
Shell Components
In the Shell Demo Application, I have created HomeView.xaml, HomeNavigator.xaml and HomeRibbonTab.xaml. Since this a demo application, I try to make Home components within the Shell, however, this can be done as a separate module component.
HomeView.xaml
The parts that are needed to be highlighted are as follows:
The Export
attribute has a "HomeView
" contract to allow the System.Uri
to be identified the resource to be represented by the Uri
instance. We can use this to navigate the view.
Uri viewNav = new Uri("/HomeView", UriKind.Relative);
regionManager.RequestNavigate(RegionNames.WorkspaceRegion, viewNav);
The ViewExport
attribute has a RegionName = RegionNames.WorkspaceRegion
contract to allow the AutoPopulateExportedViewsBehavior.OnAttach
to attach the view into WorkspaceRegion
.
HomeNavigator.xaml
HomeNavigator
is WPF UserControl
that has a ListBox
that binds to View Object Collection that is part of MenuControls.xaml Resource Dictionary.
HomeNavigator
class has ViewExport
attribute has a RegionName = RegionNames.NavigatorRegion
contract to allow the AutoPopulateExportedViewsBehavior.OnAttach
to attach the view into NavigatorRegion
. It also imports HomeNavigatorModel
as a DataContext
to bind the View Object Collection and ShowView
command.
HomeRibbonTab.xaml
HomeRibbonTab
is WPF RibbonTab
that is part of Microsoft.Windows.Controls.Ribbon
and has a RibbonGroup
and the ItemsSource="{Binding Path=ViewObjects}
". The RibbonButton
has the Label
binds to the Title
property and the Command
binds to DataContext.ShowView
. The CommandParameter
binds the ViewObject
to return into the command when executed.
RibbonTab
throws a multi-trigger binding exceptions and this has been restyle in RibbonControls.xaml to remove the binding.
HomeRibbonTab
class has ViewExport
attribute has a RegionName = RegionNames.RibbonRegion
contract to allow the AutoPopulateExportedViewsBehavior.OnAttach
to attach the view into NavigatorRegion
. It also imports HomeNavigatorModel
as a DataContext
to bind the View Object Collection and ShowView
command. Since the HomeRibbonTab
has the same functionalities, I used the same View Model.
Modules
The two modules in the demo application are basically the same to show the loose coupling of the modules. Essentially, each module has its own Navigation Menu and Ribbon Menu loaded by the AutoPopulateExportedViewsBehavior
class. The module has a ModuleController
class and interface to communicate between navigation and views. The ModuleController
is being used as MVVM pattern bridges between two different View Models rather than duplicating functionalities within the View Models.
The view binds to properties on a ViewModel
and exposes data contained in model objects. The ModuleController
is used to load data into and bind DelegateCommand
to functions. The bindings between view
and ViewModel
are simple to construct because a ViewModel
object is set as the DataContext
of a view
.
Demo Module Components
ModuleController Class and IModuleController Interfaces
The Export
attribute in the ModuleController
class exports the IModuleController
interface to hide the implementations. It has an [ImportingConstructor]
in the constructor to use the MEF constructor initialisation. It imports the IRegionManager
as the regionManager
to navigate the views and loads the data into the ViewObjects
.
NavigatorModel and RibbonTabModel Class
The two classes are basically same at the moment, however, in a real business application you may see objects inside NavigatorModel
but not the RibbonTabModel
; so the separation allows you to built different functionalities. It loads the IModuleController
interface using the [ImportingConstructor]
and assigned the properties.
Views
ModuleOneNavigator.xaml
ModuleOneNavigator
is a WPF UserControl
that has a ListBox
that binds to View Object Collection that is part of MenuControls.xaml Resource Dictionary.
ModuleOneNavigator
class has ViewExport
attribute has a RegionName = RegionNames.NavigatorRegion
contract to allow the AutoPopulateExportedViewsBehavior.OnAttach
to attach the view into NavigatorRegion
. It also imports ModuleOneNavigatorModel
as a DataCo
ntext
to bind the View
Object Collection and ShowView
command.
ModuleOneRibbonTab.xaml
ModuleOneRibbonTab
is a WPF RibbonTab
that is part of Microsoft.Windows.Controls.Ribbon
and has a RibbonGroup
and the ItemsSource="{Binding Path=ViewObjects}"
. The RibbonButton
has the Label
binds to the Title
property and the Command
binds to DataContext.ShowView
. The CommandParameter
binds the ViewObject
to return into the command when executed.
RibbonTab
throws a multi-trigger binding exceptions and this has been restyled in RibbonControls.xaml to remove the binding.
ModuleOneRibbonTab
class has ViewExport
attribute has a RegionName = RegionNames.RibbonRegion
contract to allow the AutoPopulateExportedViewsBehavior.OnAttach
to attach the view into NavigatorRegion
. It also imports ModuleOneRibbonTabModel
as a DataContext
to bind the View
Object Collection and ShowView
command.
View1.xaml and View2.xaml
The part that needed to be highlighted as follows:
The Export
attribute has a name "View1
" or "View2
" contract to allow the System.Uri
to be identified the resource to be represented by the Uri
instance. We can use this to navigate the view. In the code, I have used the view
property in the ViewObject
.
Uri viewNav = new Uri("/View1", UriKind.Relative);
regionManager.RequestNavigate(RegionNames.WorkspaceRegion, viewNav);
Conclusion
This article presents creating Prism using MEF object rather than Unity. I do not have a preference over the different methods as I have built Prism 2 using Unity and migrating Prism 4 using Unity. Changing from one to another can be quite painful and you may want to consider building another Prism project from the ground up using MEF or even stick to Unity because you are comfortable with it.
History
- 13 Sept 2011: Initial version