This article will provide a basic platform from which you can delve deeper into other aspects of Prism.
Introduction
Prism is a framework that enables development of loosely coupled applications that are flexible, maintainable and easy to test. Prism applications are made up of modules – loosely coupled functional units that encapsulate portions of an application's overall functionality. In a team setting, modules can be individually developed, tested and deployed, thus minimizing cross-team dependencies and enabling teams, or individuals in a team, to focus on specific aspects of an application. Prism can be used to develop either desktop or mobile applications, that follow the MVVM design pattern, as it supports creation of WPF, Xamarin and UnoPlatform projects.
Background
This article provides a basic overview of Prism for WPF using a sample application that displays profiles of imaginary staff members. The sample project can be cloned or downloaded from GitHub.
The sample application.
The sample application contains four projects: a WPF application project; a class library with shared code; and two Prism modules.
TIP: The easiest way to set up a Prism project is by first installing the VS Prism Template Pack extension and making use of the Prism project templates. The main project in the sample application was created using the Prism Blank App (WPF) template while the modules were added using the Prism Module (WPF) template.
Some Prism project templates.
Prism
Shell, Regions & Views
Prism applications are made up of a shell which hosts all the visual components of an application. As is the norm in Prism, and by default, the Prism app project template sets MainWindow
as the application's shell.
C#
public partial class App{ protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } ...}
A shell contains one or more regions where modules can inject views, and a view can in turn contain regions where other views can be placed. The following diagram, from the Prism documentation, highlights this setup,
To place a view into a region, modules make use of the RegionManager
which keeps track of all the regions in an application. The sample application has only one region named ContentRegion
,
XML
<mah:MetroWindow x:Class="StaffStuff.Views.MainWindow" ... xmlns:prism="http://prismlibrary.com/" xmlns:common="clr-namespace:StaffStuff.Common;assembly=StaffStuff.Common" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" ... prism:ViewModelLocator.AutoWireViewModel="True" ...> ... <Grid> <ContentControl prism:RegionManager.RegionName= "{x:Static common:RegionNames.ContentRegion}" /> </Grid></mah:MetroWindow>
The sample application has two views: A user control that displays cards containing some employee details and another user control that displays more details of a specific employee. The latter user control also contains a button for navigating back to the first view.
The two views are in a module named UIModule
and as you can probably tell, this module is UI specific. (The modules in the sample application are organized in 'horizontal layers'. If the application was organized around vertical slices each of the views would be in different modules and those modules would contain functionality specific to a view.)
UIModule
implements Prism's IModule
interface. This interface has two methods; one that is called when the module is initialized and another that's used for type registration (I set Unity as the IoC container when creating the project. The Prism project wizard offers you the option of using either Unity or DryIoc).
C#
using StaffStuff.UI.Views;using Prism.Ioc;using Prism.Modularity;using Prism.Regions;using StaffStuff.Common;namespace StaffStuff.UI{ public class UIModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { var regionManager = containerProvider.Resolve<IRegionManager>(); regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(StaffView)); } public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<StaffView>(); containerRegistry.RegisterForNavigation<StaffDetailsView>(); } }}
In OnInitialized()
, I'm requesting the RegionManager
to place StaffView
in ContentRegion
. As a result, StaffView
will be the default view that is displayed when the application is launched. In RegisterTypes()
, I'm specifying that both views should be available for navigation. This enables moving back and forth between the two views using Prism's navigation capabilities.
View Models & Navigation
Each of the views has its data context set to a view model that is wired to the view using Prism's ViewModelLocator
. Prism associates a view model with a view using a default convention which assumes that a view model is in the same assembly as the view; is in a .ViewModels
child namespace; has a name that corresponds with that of a view; and that the corresponding view is in a .Views
child namespace. (If you prefer using a different convention, you can specify a custom one.) The UI module has two view models, each associated with a specific view.
The view models inherit a base class that inherits Prism's BindableBase
and implements its INavigationAware
interface.
C#
using Prism.Mvvm;using Prism.Regions;namespace StaffStuff.UI.ViewModels{ public class ViewModelBase : BindableBase, INavigationAware { protected IRegionManager RegionManager { get; } public ViewModelBase(IRegionManager regionManager) { RegionManager = regionManager; } public virtual bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public virtual void OnNavigatedFrom(NavigationContext navigationContext) { } public virtual void OnNavigatedTo(NavigationContext navigationContext) { } }}
BindableBase
is simply Prism's implementation of INotifyPropertyChanged
while INavigationAware
enables a view model to participate in the navigation process.
StaffViewModel
contains a DelegateCommand
whose execute method instructs the RegionManager
to navigate to the staff details view – DelegateCommand
is Prism's implementation of the ICommand
interface.
C#
using System.Collections.Generic;using Prism.Commands;using Prism.Regions;using StaffStuff.Common;using StaffStuff.Common.Interfaces;using StaffStuff.Common.Models;namespace StaffStuff.UI.ViewModels{ public class StaffViewModel : ViewModelBase { private List<Employee> _employees; public List<Employee> Employees { get => _employees; set => SetProperty(ref _employees, value); } public DelegateCommand<Employee> EmployeeDetailsCommand { get; } public StaffViewModel(IStaffData staffData, IRegionManager regionManager) : base(regionManager) { Employees = staffData.GetEmployees(); EmployeeDetailsCommand = new DelegateCommand<Employee>(StaffDetails); } private void StaffDetails(Employee employee) { var parameters = new NavigationParameters(); parameters.Add(nameof(Employee), employee); RegionManager.RequestNavigate (RegionNames.ContentRegion, "StaffDetailsView", parameters); } }}
A parameter is passed in the navigation request. This parameter, an Employee
object, is added to the NavigationParameters
' collection with a key that uniquely identifies it. The key can be used by the view model of the view you're navigating to retrieve a particular object.
C#
using Prism.Commands;using Prism.Regions;using StaffStuff.Common.Models;namespace StaffStuff.UI.ViewModels{ public class StaffDetailsViewModel : ViewModelBase { private Employee _employee; public Employee Employee { get => _employee; set => SetProperty(ref _employee, value); } private IRegionNavigationJournal _journal; public DelegateCommand GoBackCommand { get; } public StaffDetailsViewModel(IRegionManager regionManager) : base(regionManager) { GoBackCommand = new DelegateCommand(GoBack); } private void GoBack() => _journal.GoBack(); public override void OnNavigatedTo(NavigationContext navigationContext) { Employee = navigationContext.Parameters[nameof(Employee)] as Employee; _journal = navigationContext.NavigationService.Journal; } }}
Navigation back to the previous view is done using the navigation journal. The navigation journal also has a GoForward()
method which can be used for forward navigation.
Module Registration
The modules in the sample application have to be registered with Prism's ModuleCatalog
so they can be loaded by the app. This is done in App.xaml.cs.
C#
public partial class App{ ... protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<ServicesModule>(); moduleCatalog.AddModule<UIModule>(); }}
Conclusion
I've covered some aspects of Prism for WPF but there are a couple of areas I didn't highlight; like the different navigation strategies, Prism's event aggragator, and composite commands. This article has hopefully provided a basic platform from which you can delve deeper into other aspects of Prism. I recommend you take a look at the various Prism sample projects on GitHub and go through the documentation to get a wider view.
History
- 1st July 2020: Initial post