Wednesday, 10 February 2010

Prism Guidelines

Due to the number of projects and files which can make up a Prism solution, it is important to adhere to conventions which are intuitive and sensible.  Maintaining these conventions takes the guesswork out of working with Prism and makes navigating through a solution predictable.
This document assumes a basic understanding of Prism, the Model-View-ViewModel (MVVM) design pattern, the Model-View-Controller (MVC) design pattern and Windows Presentation Foundation (WPF).

Prism requires that each module contain a class which implements IModule.  This effectively serves as a stub, allowing Unity to load the module.  IModule has an Initialize method that is often overridden and is used to register items in the container.

Avoid using the Initialize method to register the module’s items, instead create an entry in your unity.config file.  It is easier to maintain and does not involve code.

GOAL: To maintain a separation of UI and business logic.
It is rare that a UI requirement cannot be expressed using Xaml alone.  Effort should be made to eliminate any UI logic contained in the View. 

Views always have a corresponding ViewModel.

Setting the ViewModel
Each View should contain a setter for its ViewModel, as shown below.
This property uses the Microsoft.Practices.Unity.DependencyAttribute, which gets picked up by Unity and automatically set.

Use of Styles
Avoid defining custom styles which effect appearance within your module or view.  Instead, use styles defined in your application resource assembly.  If you need to modify a control’s behaviour through the use of a Trigger, use the BasedOn property of the Style, referencing the resource Style.
Always reference the resource Style by using a ComponentResourceKey defined in your Interfaces assembly.
Style="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type Resources:Styles}, ResourceId=DialogBorderStyleKey}}"

Use of Converters
Avoid excessive use of custom converters (IValueConverter).  Often the same can be accomplished using Triggers or DataBinding.  Converters can act as “hidden” code-behind, making testing difficult.  Always look for a Xaml solution before resorting to the use of converters.

Naming Conventions
View <name>View CustomerDetailView
Interface I<name>View ICustomerDetailView

GOAL: To encapsulate all UI-related logic in a single, testable class.

The ViewModel exists to serve the View, so use it.  Instead of using custom Converters do it in your ViewModel.

Avoid passing the IUnityContainer (or an abstract version) in your ViewModel constructor.  This hides the actual dependency and makes testing difficult.

Consider implementing INotifyPropertyChanged on your ViewModel so your View can respond to changes in the ViewModel.

Naming Conventions
ViewModel <name>ViewModel CustomerDetailViewModel
Interface I<name>ViewModel ICustomerDetailViewModel

GOAL: To provide contracts that define a public interface, separate from its implementation.

Prism relies heavily on the use of interfaces.  All entities registered in the UnityContainer must include an interface and an entity that implements the interface.

Do not include public interfaces in your implementation assemblies, as this defeats the purpose of decoupling interface from implementation.

GOAL: To provide reusable UI elements independent of implementation.

WPF UserControls differ from Views in that they do not have a related ViewModel.

Consider adding DependencyProperties to your UserControl to take advantage of DataBinding and Triggers, this makes your UserControl more versatile and reusable.

Avoid including binding to specific properties in your UserControl as this can greatly limit its reuse.

Naming Conventions
UserControl <name>UserControl AddressUserControl

GOAL: To provide static, stateless business logic and functionality across domains.

Services are normally registered in Unity as a singleton.

It is best to think of Services as a static library of methods.  If state persistency is required consider using a Controller (MVC pattern).

When consuming Services, avoid accessing them through IServiceLocator, instead explicitly use the service interface in your class constructor.  This makes for predictable testing and does not hide functionality or dependencies.

Naming Conventions
Service <name>Service LoggingService
Interface I<name>Service ILoggingService

GOAL: To provide multicast notifications across domains.

Note: The events referred to here are to be used by the EventAggregator, not standard .Net events.

Do not define events in your implementation assemblies. Since other assemblies will need to reference your event class placing them in an implementation assembly forces you to create a strong reference. Instead, place it in your interface assembly or, if appropriate, a shared core assembly.

Naming Conventions
Event <name>Event SaveCustomerEvent
Handler <name>EventHandler SaveCustomerEventHandler

GOAL: To provide an input mechanism which allows for multiple and disparate sources to invoke the same command logic.

Include command logic in your ViewModel, or if using a Controller you may want to defer execution to the Controller.

Consider using a Command Behavior AttachedProperty to convert events to Commands on controls that do not natively support ICommand.

Naming Conventions
Command <name>Command SaveCommand
Handler <name>CommandExecute SaveCommandExecute
<name>CommandCanExecute SaveCommandCanExecute

Prediction and consistency go a long way in working with Prism. Not subscribing to agreed upon conventions adds confusion and slows down development.


Schneider said...

Nice guidelines. There are not enough of these in the chaos known as MVVM.

Presumably your Views are also implemented as UserControls but not recognised as such (e.g. the naming convention?)

rgramann said...

Hi Schneider,

Thanks for your comment.

Yes, our Views are WPF UserControls. We prefer the View convention to distinguish them from ordinary UserControls, but yes, they are no different from UserControls.

Re: Chaos - I think order can only come about by agreement and adherence to coding conventions and established architectural patterns. Otherwise things can get pretty chaotic.