Introduction
Prism provides guidance designed that help you to design and
build rich, flexible, and easy-to-maintain WPF desktop applications.Prism helps
you to design and build applications using loosely coupled components that can
evolve independently and can be easily integrated into the overall application.
These types of applications are known as composite applications.Prism was the
code name for the guidance formally known as the Composite Application Guidance for WPF and
Silverlight.
Prism helps in building the application so that it is
flexible and can be easily modified or extended over time.It allows individual
parts of the application to be independently developed and tested and that can
be modified or updated later, in isolation, without affecting the rest of the
application.
The
Composite Approach
The overall application should be divided into a number of
discrete, loosely coupled, semi-independent components that can then be easily
integrated together into an application "shell" to form a coherent
solution. Applications designed and built this way are often known as composite
applications.
Composite applications provide many benefits, including the
following:
·
They allow modules to be individually developed,
tested, and deployed by different individuals or sub teams; they also allow
them to be modified or extended with new functionality more easily.
Challenges
Not Addressed by Prism
Prism does not directly address the following topics:
·
Service infrastructure design
·
Authentication and authorization
·
Application performance
·
Application versioning
·
Error handling and fault tolerance
Prism
Key Concepts
ü
Modules:
Modules are packages of functionality that can be independently developed,
tested, and (optionally) deployed. A typical Prism application is built from
multiple modules. Modules can be used to represent specific business-related
functionality (for example, profile management) and encapsulate all the views,
services, and data models required to implement that functionality. A module is
denoted by a class that implements the IModule interface. These modules,
during initialization, register their views and services and may add one or
more views to the shell.
ü
Module catalog:
In a composite application, modules must be discovered and loaded at run time
by the host application. In Prism, a module catalog is used to specify which
modules to are to be loaded, when they are loaded, and in what order. The
module catalog is used by the ModuleManager
components, which are responsible for loading the module's assemblies into the
application domain, and for initializing the module.
ü
Shell: The shell is the host
application into which modules are loaded. The shell defines the overall layout
and structure of the application.The shell also provides the top-level window
or visual element that will then host the different UI components provided by
the loaded modules.
ü Regions: Regions are
logical placeholders defined within the shell into which views are displayed.
Many common controls can be used as a region, allowing views to be
automatically displayed within controls, such as a ContentControl, ItemsControl, or TabControl. Views can be
displayed within a region programmatically or automatically. Prism also
provides support for implementing navigation with regions. Regions can be
located by components through the RegionManager
component, which uses RegionAdapter
and RegionBehavior components to
coordinate the display of views within specific regions.
ü
EventAggregator: Components in a composite
application often need to communicate with other components in the application
in a loosely coupled way (eg. one viewmodel to another). To support this, Prism
provides the EventAggregator
component, which implements a pub-sub (Publish-Subscribe) event mechanism,
thereby allowing components to publish events and other components to subscribe
to those events without referencing each other.
ü
Dependency injection
container: The
Dependency Injection (DI) pattern is used throughout Prism to allow the
dependencies between components to be managed. Dependency injection allows
component dependencies to be fulfilled at run time, and it supports
extensibility and testability. Prism is designed to work with Unity or MEF.
ü
Bootstrapper:
A bootstrapper is a class that is responsible for the initialization of an
application built using the Prism Library .The Bootstrapper
component is used by the application to initialize the various Prism
components. It is used to initialize the dependency injection container (UnityBootstrapper
or MefBootstrapper). It is also used to configure and initialize the
module catalog and the shell's view and view model.
àBootstrapping
is the process of registering and configuring the container during the
application startup (the application that is built on prism).
A Prism application typically consists of a shell project
and multiple module projects.
ü
Model: Model classes encapsulate
the application data and business logic. They are used as part of the MVVM or
MVP patterns.
ü
View: Views are UI controls and
are used in conjunction with the MVVM or Model-View-Presenter (MVP) patterns,
which are used to provide a clean separation of concerns between the UI and the
application's presentation logic and data. Views use data binding to interact
with view model and presenter classes.
ü
View model: View models are
classes that encapsulate the application's presentation logic and state. They
are part of the MVVM pattern. View models encapsulate much of the application's
functionality. Presenters are similar to view models in that they encapsulate
the presentation logic and state. They are used as part of the MVP pattern.
Both view models and presenters define properties, commands, and events, to
which controls in the view can data-bind.
ü
Navigation: Navigation is defined as the
process by which the application coordinates changes to its UI. Prism supports
two styles of navigation: state-based navigation, where the state of an
existing view is updated to implement simple navigation scenarios, and view-switching
navigation, where new views are created and old views replaced within the
application's UI.
Dependency
Injection Container
There are specific advantages using container:
ü
A container injects module dependencies into the
module when it is loaded.
ü
A container is used for registering and
resolving view models and views.
ü
A container injects the composition services,
such as the region manager and the event aggregator.
MEF provides several capabilities that Unity does not:
ü
It discovers assemblies in a directory at
runtime.
ü
It automatically exports derived types
(IRegionManager and IEventAggregator).
ü
It is deployed with the .NET Framework.
Containers are used for two primary purposes, namely
registering and resolving.
Usually, if you are replacing the container, you will also
need to provide your own container-specific bootstrapper by implementing IServiceLocator interface.
Module
A module is a logical collection of functionality that can
be separately developed, tested, deployed, and integrated into an application. Each
module has a central class that is responsible for initializing the module and
integrating its functionality into the application. That class implements the IModule interface. The IModule interface has a single
method, named Initialize,
within which you can implement whatever logic is required to initialize and
integrate the module's functionality into the application. (eg. register views
into composite user interfaces)
public class MyModule : IModule
{
public void Initialize()
{
// Do something here.
}
}
The modules are loaded at run-time by Module catalog.
The catalog contains information about the modules to be loaded, their
location, and the order in which they are to be loaded. The modules are discovered from assembly or from local directory
containing assembly by Assembly catalog or by Directory catalog. The
modules are then initialized. This means creating instances of the module class
and calling the Initialize
method on them via the IModule
interface.
Modules
in MEF :
àRegistering
Modules in Code Using MEF
MEF automatically discover the (module) types through ModuleExport attribute which is
applied to module classes. The following is an example.
[ModuleExport(typeof(ModuleB))]
public class ModuleB : IModule
{
...
}
AssemblyCatalog
class is used to discover all the exported module classes in an assembly. By
default, the Prism MefBootstrapper
class creates an AggregateCatalog
(Module catalog in MEF) instance. You can then override the ConfigureAggregateCatalog
method to register assemblies, as shown in the following code
example.
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
//Module A is referenced in in the project and directly in code.
this.AggregateCatalog.Catalogs.Add(
new AssemblyCatalog(typeof(ModuleA).Assembly));
this.AggregateCatalog.Catalogs.Add(
new AssemblyCatalog(typeof(ModuleC).Assembly));
}
àDiscovering
Modules in a Directory Using MEF
MEF provides a DirectoryCatalog
that can be used to load the assemblies containing modules (and other MEF
exported types) from directory. In this case, you override the ConfigureAggregateCatalog
method to register the directory. This approach is only available in WPF.
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");
this.AggregateCatalog.Catalogs.Add(catalog);
}
àSpecifying
Dependencies in Code Using MEF
For WPF applications using MEF, use the ModuleExport
attribute, as shown here.
[ModuleExport(typeof(ModuleA), DependsOnModuleNames = new string[] { "ModuleD" })]
public class ModuleA : IModule
{
...
}
àSpecifying
On-Demand Loading Using MEF
If you are using MEF and the ModuleExport attribute
for specifying modules and module dependencies, you can use the InitializationMode
property to specify that a module should be loaded on demand, as shown here. [InitializationMode.OnDemand or
InitializationMode.WhenAvailable]
[ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)]
public class ModuleC : IModule
{
}
MVVM in Prism
·
In Prism, we can use NotificationObject
class which implements
INotifyPropertyChanged interface. An inherited view model class can
raise the property change event by either invoking RaisePropertyChanged with the
property name specified.
·
The Prism DelegateCommand
class encapsulates two delegates (Execute and CanExecute) that each reference a
method implemented within your view model class. It inherits from the DelegateCommandBase class, which
implements the (IActionAware) ICommand
interface's Execute and CanExecute methods by invoking these delegates. You
specify the delegates to your view model methods in the DelegateCommand class
constructor.
àInvoking Command Objects from the
View
1) Using Command-Enabled
Controls :
Control can be easily data bound to a command object through
the Command property.
<Button Command="{Binding Path=SubmitCommand}" CommandParameter="SubmitOrder"/>
A command parameter can also be optionally defined using the
CommandParameter property. It
will be passed as the argument to the command's Execute
method.
public class QuestionnaireViewModel
{
public QuestionnaireViewModel()
{
this.SubmitCommand = new DelegateCommand<object>(
this.OnSubmit, this.CanSubmit );
}
public ICommand SubmitCommand { get; private set; }
private void OnSubmit(object arg) {...}
private bool CanSubmit(object arg) { return true; }
}
Additionally, if a CanExecute
handler is specified, the button will be automatically disabled if CanExecute
returns false, and it will be enabled
if it returns true.
2) Using Behaviors
An alternative approach is to use Expression Blend
interaction triggers and InvokeCommandAction
behavior.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBox Text="{Binding
ProposalInformation.AgentBrokerNumber1}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding
RetrieveAgentCommand1}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
This approach can be used for any control to which you can
attach an interaction trigger. InvokeCommandAction
does not automatically enable or disable the control based on the command's CanExecute value. To implement
this behavior, you have to data bind the IsEnabled
property of the control directly to a suitable property on the view model.
Command-Enabled Controls vs. Behaviors
Using command enabled control; controls will invoke the
specified command when the user interacts with the control in a specific way. For
example, for a Button
control, the command will be invoked when the user clicks the button. This
event associated with the command is fixed and cannot be changed.
However, behaviors can be associated with a range of events
raised by the control, and they can be used to conditionally invoke an
associated command object in the view model. In other words, behaviors can
address many of the same scenarios as command-enabled controls, and they may
provide a greater degree of flexibility and control.
àInvoking Command Methods from the
View
Instead of using InvokeCommandAction, you use the CallMethodAction. The following
code example calls the (parameter-less) Submit method
on the underlying view model. The TargetObject
is bound to the underlying data context (which is the view model) by using the {Binding} expression. The Method parameter specifies the
method to invoke.
<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding}" Method="Submit"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Prism UI Layout Concepts
The root object in a composite application is known as the shell.
The shell acts as a master page for the application. The shell contains one or
more regions. Regions are place holders for content that will be loaded at run
time. Regions are attached to UI elements such as a ContentControl, ItemsControl,
TabControl or a custom control and manage the UI element's content.
Region content can be loaded automatically or on-demand, depending on the
application requirements.
Typically, a region's content is a view. A view encapsulates
a portion of your UI that you would like to keep as decoupled as possible from
other parts of the application. You can define a view as a user control, data
template, or even a custom control.
A region manages the display and layout of views. Regions
can be accessed in a decoupled way by their name and support dynamically adding
or removing views. Typically, the shell is a part of the WPF application
project.
Region
Manager
The RegionManager
class is responsible for creating and maintaining a collection of regions for
the host controls. The RegionManager
can create regions in code or in XAML. The RegionManager.RegionName
attached property is used to create a region in XAML by applying the attached
property to the host control.
The RegionManager
uses a control-specific adapter that associates a new region with the host
control.
Region
Adapter
Region adapters are responsible for creating a region and
associating it with the control. This allows you to use the IRegion interface to manage the UI
control contents in a consistent way.
Prism Library provides the following three region adapters:
ü
ContentControlRegionAdapter: This adapter
adapts controls of type System.Windows.Controls.ContentControl and derived
classes.
ü
SelectorRegionAdapter: This adapter
adapts controls derived from the class System.Windows.Controls.Primitives.Selector,
such as the System.Windows.Controls.TabControl control.
ü
ItemsControlRegionAdapter: This adapter
adapts controls of type System.Windows.Controls.ItemsControl and derived
classes.
Displaying
Views in a Region When the Region Loads
Modules register views with a registry.
The Prism Library defines a standard registry, RegionViewRegistry,
to register views for these named locations. You can directly register a view
type with the region, in which case the view will be constructed by the
dependency injection container and added to the region when the control hosting
the region is loaded.
this.regionManager.RegisterViewWithRegion("MainRegion", typeof(EmployeeView));
Navigation
Region navigation is a form of view injection. When a
navigation request is processed, it will attempt to locate a view in the region
that can fulfill the request. If it cannot find a matching view, it calls the
application container to create the object, and then injects the object into
the target region and activates it.
this.regionManager.RequestNavigate(RegionNames.SecondaryRegion,
new Uri("/NewsReaderView", UriKind.Relative));
Sharing Data between Multiple
Regions:
The Prism Library provides multiple approaches to
communicating between views, depending on your scenario. The region manager
provides the RegionContext property
as one of these approaches.
Navigation in Prism
Navigation is defined as the process by which the
application coordinates changes to its UI as a result of the user's interaction
with the application. Prism differentiates between the two styles of navigation
described earlier.
State-Based Navigation:
In state-based navigation, the view that represents the UI
is updated either through state changes in the view model or through the user's
interaction within the view itself. In this style of navigation, instead of
replacing the view with another view, the view's state is changed. Depending on
how the view's state is changed, the updated UI may feel to the user like
navigation.
This style of navigation is suitable in the following
situations:
ü
The view needs to display the same data or
functionality in different styles or formats.
ü
The view needs to change its layout or style
based on the underlying state of the view model.
This style of navigation is not suitable for situations in
which the UI has to present different data to the user or when the user has to
perform a different task.
The State-Based Navigation application uses Expression
Blend's DataStateBehavior
data-bound to a radio button to switch between two visual states that are
defined using the visual state manager, one button to show the contacts as a
list and one button to show the contacts as icons.
<DataStateBehavior
Binding="{Binding IsChecked, ElementName= ShowAsListButton}"
TrueState="ShowAsList" FalseState="ShowAsIcons"/>
As the user clicks the Contacts or Avatar
radio buttons, the visual state is toggled between the ShowAsList
visual state and the ShowAsIcons visual state.
View-Based Navigation:
It can be accomplished by replacing one view within the
application's UI with another.
Basic Region Navigation
You can programmatically initiate navigation within region
by using the RequestNavigate
method defined by the INavigateAsync interface.
IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion",
new Uri("InboxView", UriKind.Relative));
By default, the navigation URI specifies the name of a view
that is registered in the container. In MEF only the contract name is used.
Therefore, as long as the view is exported with a contract name that matches
the name in the URI request, the view can be successfully constructed.
During navigation, the specified view is instantiated, via
the container or MEF, along with its corresponding view model and other
dependent services and components. After the view is instantiated, it is then
added to the specified region and activated.
private void SelectedEmployeeChanged(object sender, EventArgs e)
{
...
regionManager.RequestNavigate(RegionNames.TabRegion,
"EmployeeDetails", NavigationCompleted);
}
private void NavigationCompleted(NavigationResult result)
{
...
}
The RequestNavigate method also allows you to
specify a callback method, or a delegate, which will be called when navigation
is complete.
View and View Model Participation in Navigation
The INavigationAware interface enables the views and
view models to participate in navigation. You can implement this interface on
the view or (more commonly) the view model. By implementing this interface,
your view or view model can opt-in to participate in the navigation process.
During navigation, Prism checks to see whether the view /viewmodel
implements the INavigationAware
interface; if it does, it calls the required methods during navigation. This
interface allows the view or view model to participate in a navigation
operation.
The INavigationAware interface defines three
methods;
public interface INavigationAware
{
bool IsNavigationTarget(NavigationContext navigationContext);
void OnNavigatedTo(NavigationContext navigationContext);
void OnNavigatedFrom(NavigationContext navigationContext);
}
The IsNavigationTarget method allows an existing
(displayed) view or view model to indicate whether it is able to handle the
navigation request. For example, a view displaying customer information can be
updated to display a different customer's information.
The OnNavigatedFrom and OnNavigatedTo methods
are called during a navigation operation. If the currently active view in the
region implements this interface (or its view model), its OnNavigatedFrom
method is called before navigation takes place. The OnNavigatedFrom
method allows the previous view to save any state or to prepare for its
deactivation or removal from the UI, for example, to save any changes that the
user has made to a web service or database.
If the newly created view implements this interface (or its
view model), its OnNavigatedTo method is called after navigation is
complete. The OnNavigatedTo method allows the newly displayed view to
initialize itself.
Passing Parameters during Navigation
To implement the required navigational behavior in your
application, you will often need to specify additional data during navigation
request than just the target view name. The NavigationContext object
provides access to the navigation URI, and to any parameters that were
specified within it. You can access the NavigationContext from within
the IsNavigationTarget, OnNavigatedFrom, and OnNavigatedTo
methods.
Prism provides the UriQuery class to help specify
and retrieve navigation parameters. You can use this class to add navigation
parameters to the navigation URI before you initiate navigation and to access
individual parameters during navigation. The UriQuery class maintains a
list of name-value pairs, one for each parameter.
The following code example shows how to add individual
parameters to the UriQuery instance so that it can be appended to the
navigation URI.
Employee employee = Employees.CurrentItem as Employee;
if (employee != null)
{
UriQuery query = new UriQuery();
query.Add("ID", employee.Id);
_regionManager.RequestNavigate(RegionNames.TabRegion,
new Uri("EmployeeDetailsView" + query.ToString(), UriKind.Relative));
}
You can retrieve the navigation parameters using the Parameters
property on the NavigationContext object. This property returns an
instance of the UriQuery class, which provides an indexer property to
allow easy access to individual parameters.
public void OnNavigatedTo(NavigationContext navigationContext)
{
string id = navigationContext.Parameters["ID"];
}
Navigating to Existing Views
public bool IsNavigationTarget(NavigationContext navigationContext)
{
string id = navigationContext.Parameters["ID"];
return _currentCustomer.Id.Equals(id);
}
If the IsNavigationTarget method always
returns true, regardless of the navigation parameters, that view
instance will always be re-used. This allows you to ensure that only one view
of a particular type will be displayed in a particular region.
Event Aggregation
The Prism Library provides an event mechanism
that enables communications between loosely coupled components in the
application. This mechanism, based on the event aggregator service, allows
publishers and subscribers to communicate through events and still do not have
a direct reference to each other. The event aggregator also allows for multiple
publishers and multiple subscribers.
The real work of connecting publishers and
subscribers is done by the CompositePresentationEvent class. This is the
only implementation of the EventBase class that is included in the Prism
Library. This class maintains the list of subscribers and handles event
dispatching to the subscribers.
Creating and Publishing Events
The following sections describe how to create,
publish, and subscribe to CompositePresentationEvent using the IEventAggregator
interface.
Creating an Event
The CompositePresentationEvent<TPayload>
is intended to be the base class for an application's or modules specific
events. TPayLoad is the type of the event's payload. The payload is the
argument that will be passed to subscribers when the event is published.
public class TickerSymbolSelectedEvent : CompositePresentationEvent<string>{}
Publishing an Event
Publishers raise an event by retrieving the
event from the EventAggregator and calling the Publish
method. To access the EventAggregator, you can use dependency injection
by adding a parameter of type IEventAggregator to the class constructor.
For example, the following code demonstrates
publishing the TickerSymbolSelectedEvent.
this.eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish("STOCK0");
Subscribing to Events
Subscribers can enlist with an event using one of the
Subscribe method overloads available on the CompositePresentationEvent class.
By default, the subscriber receives the event
on the publisher's thread. The CompositePresentationEvent provided with
the Prism Library can assist by allowing the subscriber to automatically
receive the event on the UI thread.
public void Run()
{
...
this.eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews, ThreadOption.UIThread);
);
}
public void ShowNews(string companySymbol)
{
this.articlePresentationModel.SetTickerSymbol(companySymbol);
}
The following options are available for ThreadOption:
- PublisherThread.
Use this setting to receive the event on the publishers' thread. This is
the default setting.
- BackgroundThread.
Use this setting to asynchronously receive the event on a .NET Framework
thread-pool thread.
- UIThread.
Use this setting to receive the event on the UI thread.
Unsubscribing from an Event
If your subscriber no longer wants to receive
events, you can unsubscribe by using your subscriber's handler or you can
unsubscribe by using a subscription token.
The following code example shows how to
directly unsubscribe to the handler.
FundAddedEvent fundAddedEvent = this.eventAggregator.GetEvent<FundAddedEvent>();
fundAddedEvent.Subscribe(FundAddedEventHandler, ThreadOption.PublisherThread);
fundAddedEvent.Unsubscribe(FundAddedEventHandler);
IPartImportsSatisfiedNotification
Notifies a part when its imports have been satisfied
(System.ComponentModel.Composition)
Method:
OnImportsSatisfied:
|
It is called when a part's imports have been satisfied
and it is safe to use.
|
In some situations it may be important for your
class to be notified when MEF is done with the import process for your class
instance. If that's the case implement the [System.ComponentModel.Composition.IPartImportsSatisfiedNotification]
interface. This interface has only a single method: OnImportsSatisfied, which
is called when all imports are satisfied.
By Abhayashis Mohanty
Thanks for reading my blog !!
Stay tune for more update on MVC, MVVM, MEF, WPF, WCF and many more....