Prism v4 Region Navigation Pipeline
Updated on 10/14/2010
This blog post was updated on 11/8/2010 and requires Prism v4 Drop 10 or later.
The code download was updated, the Region Navigation diagram was updated, along with adding the below Region.NavigationService.NavigationFailed Event section.
Introduction
Navigation guidance has been added to Prism v4. In addition to chapter on Navigation there are two Navigation Quick Starts along with the two Reference Implementations that demonstrate various techniques for application navigation.
This article will focus on the new Prism v4 Region Navigation guidance and Prism Library API's that provide navigation within a region.
Prerequisites
A general knowledge of Prism regions, modules and MEF is required to understand this article and demo application.
Region Navigation
The below image is a diagram of the Region Navigation Pipeline. (Click image to see full size image)
Region Navigation Pipeline Learning Assets
In addition to the above flow diagram this article's download includes the, WPF Prism v4 Navigation Timing Cycle application. This application traces method calls, events and object construction during a navigation request. The application illustrates straightforward navigation requests, navigating backwards, closing forms as well as throwing exceptions at different locations during the navigation request. As an added bonus the application also demonstrates how to retrieve metadata from objects in a region to display a list of the objects and a navigation link to display them.
In the below image pictures the application after two navigation requests have completed. The red rows in the DataGrid are the first entry for each navigation request. The DataGrid automatically scrolls to the last entry each time data is added to the DataGrid. Filtering is accomplished by clicking the desired filter RadioButton above the DataGrid.
Each object visible or not, in the MainContentRegion, has a corresponding blue Hyperlink control that is displayed on the left. The Hyperlink renders the Uri used to navigate to the object. Hyperlinks also have rich ToolTips the display the contents of the bound metadata object.
You can watch the tutorial video here which covers using the included demo application.
Overview
Prism v4 adds a rich Region Navigation API to the Prism Library. The Navigation API uses an opt-in model so that objects can elect to participate in the navigation pipeline as desired. To opt-into the various Navigation API calls, objects implement the required Interface(s). The Interface names are indicated in the above navigation pipeline flow diagram.
The Navigation API interacts with both the currently active and navigation destination objects within a region. The above navigation pipeline flow diagram indicates which object is the target of each method call; Active object or destination object.
The Navigation API supports navigating to:
- View (View first)
- View with ViewModel (View first)
- ViewModel (ViewModel first, view created by DataTemplate resource lookup)
If you look at the navigation pipeline diagram, you'll notice that most of the boxes indicate that the Navigation API interacts with the View and ViewModel. It does this by casting the object to the Interface that is indicated on the diagram. If the object implements the Interface, the specified method will be called.
The Navigation API is Model-View-ViewModel (M-V-VM) friendly. During navigation request processing, the Navigation API first casts the object to the Interface then calls the method. It then casts the object's DataContext to the Interface and calls the method. This allows both a View and ViewModel to participate in the navigation pipeline.
Your application View and ViewModels do not have to implement all of the navigation Interfaces. Views in your M-V-VM applications are not required to have any knowledge that a Navigation API even exists. You can put all your navigation logic in your ViewModels. The demo application included with this article only displays View navigation information for purposes of instruction only.
Once you have mastered Region Navigation you'll quickly discover that you can create one or two ViewModel base classes that can handle most of the Navigation API Interface methods and events. Then your concrete ViewModels only implement the methods the application requires and allow the base class to handle the rest of the method calls.
Motivation
Some who have never used Prism Regions before or those that currently use Regions may be asking, "What benefits does the Region Navigation API provide?"
In addition to the benefits of using Prism Regions, the Navigation API provides these features:
- Interacts with the container to handle object construction (MEF, Unity, other containers)
- Is the same API for WPF, Silverlight and integration with Silverlight Frame Navigation API
- Note: Silverlight Frame Navigation integration is not supported out of the box. An example with supporting code will be posted on this blog soon.
- Navigation journal that enables navigating back
- Rich set of navigation methods and events to navigate, monitor and veto requests
- Objects can opt-in to be removed from the Region when navigated away from
- Navigation Request Callback provides a simple method for verifying a navigation request success
- With most or all navigation related code in your ViewModel, you can easily test your code
Navigation Pipeline Walk-Through
This granular walk-through will step you through the navigation flow diagram and included application code. Steps that are optional will be marked as such.
INavigateAsync.RequestNavigate
_regionManager.RequestNavigate(RegionNames.MainContentRegion, ViewNames.ApplicationMenuView, Callback);
<ContentControl
Grid.Column="1" Grid.Row="2" Margin="6,0"
prism:RegionManager.RegionName="{x:Static constants:RegionNames.MainContentRegion}" />
Use the RegionManager's RequestNavigate method to initiate navigation.
The first argument is the region name that was assigned by the RegionManager.RegionName attached property.
The second argument can either be string or Uri object. The string or object of the Uri must be the same as the name used to register the object in the application container. When using MEF, objects can be registered using the Export attribute and supplying a string as done below. When using an IOC container, ensure that objects added to the container are named with the same information provided to the Export attribute.
Notice that the above RequestNavigate method second argument matches the value in the below Export attribute.
[Export(ViewNames.ApplicationMenuView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class ApplicationMenuView : UserControl, IPartImportsSatisfiedNotification, IConfirmNavigationRequest {
The take-way here is that the string in the Export attribute, must match the string or object of the Uri. Valid examples:
- ItemView (this application uses the short type name)
- WPFPrismv4Navigation.UI.ItemView (you could also use the full type name)
The third optional argument is the Action<NavigationResult> Callback. This delegate will be called at the end of navigation request processing, passing the result of the navigation request. This delegate is where unsuccessful navigation requests are handled.
The Callback delegate is a good candidate for being implemented in a ViewModel base class. You could also inject the Callback handler into the ViewModel during construction. This way, any requests that do not process as expected can be handled in a central location.
Remember
The string in the Export attribute or IOC object registration, must match the string or object of the Uri.
Optional: IConfimNavigationRequest.ConfirmNavigationRequest
void IConfirmNavigationRequest.ConfirmNavigationRequest(
NavigationContext navigationContext, Action<Boolean> continuationCallback) {
//perform application logic and return a Boolean in the continuationCallback
continuationCallback(true);
}
After calling this method, the navigation pipeline pauses the navigation request processing. Views or ViewModels that implement this Interface must call the provided Callback otherwise navigation will not precede. If a subsequent navigation request is issued and this Callback has not been called, the original request will be discarded.
The driving force behind this Callback being asynchronous is the fact that Silverlight does not have a blocking dialog box capability. Silverlight developers can display a ChildWindow object and then use that response to reply to the Callback. WPF developers can use a blocking modal dialog or a non-blocking implementation similar to Silverlight.
Use this interface if you want to prevent the user from navigating away from a view before the view is completed. Since the Navigation APIs' allow you to navigate away and then return to a form, you may find that you don't need to prevent users from navigating away from a form in progress, since they can easily navigate back.
Remember
The asynchronous Callback stops the navigation request until the Callback is called.
Optional: INavigationAware.OnNavigatedFrom
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext) {
}
Implementers can use this method to save state of the currently active view. For example, I like to record which UI control has focus so that when the view is navigated back to, I can restore focus to the UI control that previously had focus.
Remember
Use this method to save state of your View or ViewModel if required.
Optional: INavigationAware.IsNavigationTarget
While implementing this Interface is optional, this method is critical to managing multiple instances of the same objects. For example, this method enables a region to contain multiple instances of a CustomerView, each editing a different customer record. This method provides the required API to enable this scenario.
Boolean INavigationAware.IsNavigationTarget(NavigationContext navigationContext) {
String item = null;
if(navigationContext.Parameters != null) {
item = navigationContext.Parameters[Constants.Global.Item];
}
Boolean result = this.CurrentItem == item;
return result;
}
Implementers use this method to inform the Navigation API that they are the target of the current navigation request.
The Navigation API queries each object in the region that matches the type being navigated to. Individual objects then use this method to communicate to the Navigation API that they are in-fact the destination object.
Implementers use the Parameters passed as part of the NavigationContext to determine if it matches the current request. In the above code snippet, the previously stored Item value is compared to the Item value in the query string parameter. The method then returns the Boolean value of that comparison.
Remember
This method is critical to managing multiple instances of the same object that are differentiated by the record key they are editing.
Parse the NavigationContext's Parameters, compare to the current state of your object and return a value indicating if the object is the target of the request or not.
Object Construction and or Activation
If the destination object is located, the NavigationService will activate it if it implements IActiveAware.
If the destination object is not located, the NavigationService will use the container to create the object, add it to the region and then active it if it implements IActiveAware.
Remember
The string in the Export attribute or IOC object registration, must match the string or object of the Uri otherwise the container will not be able to create it.
Optional: IRegionMemberLifeTime.KeepAlive
Boolean IRegionMemberLifetime.KeepAlive {
get {
return _keepAliveState;
}
}
By default, the RegionManager does not destroy an object when a different object becomes the active object in the region.
Developers can use this method to tell the Navigation API to remove the object after navigation. Since the object is no longer referenced by any object, it becomes available for garbage collection.
The below code snippet demonstrates how the included application has its objects removed when the Close button is pressed.
The below method sets _keepAliveState to false and then initiates a navigation. During this navigation request the above KeepAlive property will be checked by the Navigation API, false will be returned and the object will be removed from the region and garbage collected.
void CloseExecute() {
if(_navigationJournal.CanGoBack) {
_keepAliveState = false;
_navigationJournal.GoBack();
}
}
Remember
Implement this method to have objects removed from a region and destroyed when they are navigated away from.
Optional: IRegionNavigationService.Navigating
void NavigationService_Navigating(object sender, RegionNavigationEventArgs e) {
this.Logger.Log(Global.RegionNavigatingTo + e.Uri);
}
This event is raised by the Region Navigation Service each time the region is navigating.
Remember
Use this top level event to perform an action each time a region is navigating.
Optional: INavigationAware.OnNavigatedTo
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext) {
if(_navigationJournal == null) {
_navigationJournal = navigationContext.NavigationService.Journal;
}
if(_originalTargetUriString == null) {
_originalTargetUriString = navigationContext.Uri.ToString();
}
if(navigationContext.Parameters != null && this.CurrentItem == null) {
this.CurrentItem = navigationContext.Parameters[Constants.Global.Item];
}
}
Implementers use this method to be notified by the Navigation API that they are they have been navigated to. This allows objects to execute code each time they are navigated to.
Implementers can easily determine if this is the initial or subsequent navigation to the object. In the above code snippet you can see that the CurrentItem is only set if it is null. In the above code, if the CurrentItem is null, this indicates that this is the initial navigation to this object. If the CurrentItem is not null, this indicates the object is being navigated back to.
I use this method to initiate loading of data and to restore focus to the UI Control that had focus when the object was navigated away from.
You should not allow unhandled exceptions thrown in this code to bubble back up to the Navigation API because the object has already been navigated to at this point. If an exception does get thrown, catch it and handle it just like any other exception that occurs when the forum is being used. If you allow the exception to be bubbled back up to the Navigation API's, the navigation pipeline will stop and not complete; however the object has been navigated to; creating a disconnect.
Please read the notes in the ItemViewModel OnNavigatedTo method. These comments describe how to properly handle exceptions that are thrown by code executing in this method.
Remember
Use this method to initiate code the View or ViewModel needs run each time it is brought into view.
Do not allow unhandled exceptions in this method to bubble back up through the Navigation API. Instead, handle the exception.
INavigateAsync.NavigationRequest Callback
void Callback(NavigationResult result) {
_logger.Log(Constants.Global.NavigationRequestResultsCallBack + NavigationResultParser.Parse(result));
}
This Callback is an optional argument in the original RequestNavigate method call.
If supplied, the Callback is called with a NavigationResult object that is a rich set of properties allowing the original caller to evaluate the results of the navigation request and take required action.
For example, if the Callback handler detects that the navigation request failed, it can display a dialog informing the user of the problem.
In the above code snippet, the demo application is logging the method call with the NavigationResult information.
Developers should consider putting a Callback handler in a ViewModel or View base class. Doing this will limit the repeated boiler maker code to handle navigation errors.
Remember
Use the Callback to display errors from the navigation request.
Region.NavigationService.Navigated Event
void NavigationService_Navigated(object sender, RegionNavigationEventArgs e) {
if(dg.Items.Count > 0) {
var border = VisualTreeHelper.GetChild(dg, 0) as Decorator;
if(border != null) {
var scroll = border.Child as ScrollViewer;
if(scroll != null) {
scroll.ScrollToEnd();
}
}
}
}
The above code snippet is from the demo application ShellView. This event handler scrolls the DataGrid to the bottom each time the region is navigated.
The ShellViewModel in demo application uses this event to reload the metadata from each object in the region, each time the region is navigated.
Remember
Use this top level event to perform an action each time a region is navigated.
Region.NavigationService.NavigationFailed Event
/// <summary>
/// Handles the NavigationFailed event of the NavigationService.
///
/// Developers you can use this event handler as a central place to
/// display a navigation failed message. This handler can come in handy
/// when initiating navigation from XAML where no call back is available.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="Microsoft.Practices.Prism.Regions.RegionNavigationFailedEventArgs"/> instance containing the event data.</param>
void NavigationService_NavigationFailed(object sender, RegionNavigationFailedEventArgs e) {
this.Logger.Log(String.Format(Constants.Global.NavigationFailedMessage_FormatString, e.Uri.ToString(), e.Error.Message));
// demo only. In a real-world application, use a dialog service or interaction request to display messages.
MessageBox.Show(String.Format(Constants.Global.NavigationFailedMessage_FormatString, e.Uri.ToString(), e.Error.Message),
Constants.Global.NavigationFailed, MessageBoxButton.OK, MessageBoxImage.Error);
}
The above code snippet is from the demo application ShellView. This event handler provides a central handler for NavigationFailed event. When initiating a navigation request from XAML, there is no opportunity to capture the callback. This handler provides that capability and allows you to display the error message to the user.
Remember
Use this top level event to perform an action each time a region navigation fails.
Non-Linear Navigation
You do not need to implement the Non-Linear Navigation interfaces if you don't want to. The Prism v4 Navigation API does not have any dependencies on this code.
Background
The term Non-Linear Navigation was coined by Billy Hollis in one of his PDC presentations. He also did a dnrTV video. The application he demonstrated was a product he wrote for a customer. He spoke about the ability to hyperlink to any form or data record from anywhere in the application.
A while back I wrote WPF Non-Linear Navigation API and presented it in the BBQ Shack blog post. Additionally, I did a proof-of-concept Non-Linear Navigation in Silverlight 4 blog post.
After spending the last few weeks on the Prism Team, I've come to deeply respect the team members, their commitment to excellence and customer satisfaction.
If you are looking for a complete Navigation API for your projects, take the time to read and study the guidance and example code in the Prism v4 Guidance and Library. You'll be glad you did.
I will be updating my own blogs posts to use this API because it is better and supported.
Motivation
In a modern line of business application, it is important to provide your end users the ability to multi-task. For example they could be working on a Customer record; someone comes to their desk and needs them to view history in another module. Then the phone rings and now they need to do another task, but don't want to cancel out of the current task.
The application should allow them to quickly navigate to another task, and then return to the original task. This workflow should be easy and simple.
The application should also make it easy for a user to visually see the number of active views that are hidden and easily get back to them.
Included Demo Application
Implementing Non-Linear Navigation is actually very simple. It starts with a container like a region that contains multiple objects. The objects can all be visible or one can be visible and the others hidden until they are navigated to.
This application uses a collection of metadata to surface the objects in region.
The above Hyperlink Controls are created using the metadata sourced from the region objects.
The ShellViewModel NavigationService_Navigated event handler is used to iterate over each object in the region that implements the INonLinearNavigationObject Interface.
This Interface provides a common way for objects to communicate metadata. The below class diagram shows the metadata properties. In a future blog post I'll show this metadata being used in a real-world scenario. Please see the detailed comments in this Interface's source code.
Video
The demo application video can be viewed here.
Note: You can view the high resolution version of the .wvm video file by:
- Clicking on the video link
- Log into Vimeo
After logging in, you’ll be given access to the high resolution video download. There is no cost associated with this.
Download
The link to download the demo application is located at the bottom of this article.
Requirements: you must download Prism v4 and run the RegisterPrismBinaries.bat batch file. The Prism v4 Readme covers this file in detail. If you do not want to run this batch file, you’ll need to remove and re-add the references to the Prism assemblies.
Comments
Microsoft values your opinion about our products, guidance, documentation and samples.
Thank you for your feedback and have a great day,
Karl Shifflett
Patterns & Practices Prism Team
WPFPrismv4NavigationUpdated11-8-2010.zip
Comments
Anonymous
October 07, 2010
Could you explain ViewModel First approach for navigation more deeply?Anonymous
October 07, 2010
Konstantin, In the M-V-VM world, there are two approaches to showing the UI, View first and ViewModel first. Many developers have written about this. Can I suggest searching the web for: viewmodel first Additionally, on the 14th of October, Prism v4 Drop 10 documentation will have a chapter on M-V-VM that explains this M-V-VM concept. Have a great day, KarlAnonymous
October 17, 2010
Hi Karl, Thanks for the article and demo app. Our prism application has a bunch of modules (implementing IModule) which are registered by a UnityBootstrapper. You mention that "The Navigation API supports navigating to: •View (View first) •View with ViewModel (View first) •ViewModel (ViewModel first, view created by DataTemplate resource lookup) " Does this mean that the Navigation API can't interact with modules, or am I misunderstanding something? Cheers, JamesAnonymous
October 19, 2010
James, No your are good to go. The Views or ViewModels in your modules can be navigated to. Cheers, KarlAnonymous
October 26, 2010
Could you provide an example based on Unity?Anonymous
October 26, 2010
Hi Karl, I started using the region based navigation, but one thing I can't find is how to recognize the navigation direction. Regular SL navigation exposes this via NavigationMode property on NavigationCancelEventArgs: msdn.microsoft.com/.../system.windows.navigation.navigatingcanceleventargs.navigationmode.aspx I think this informaiton should be also exposed on the NavigationContext because it is sometimes important to the logic to determine if we navigate to new page or going back the stack. In my scenario I'm trying to add page transition animation and would like to run different animaitons for going forward or back.Anonymous
October 27, 2010
Matjaz, I'll be publishing an application next week that uses Unity and Region Navigation. Check my blog next week.for the post. Cheers, KarlAnonymous
October 27, 2010
Szymon, We are looking into your request for the NavigationMode property. Thank you for reporting this, KarlAnonymous
October 28, 2010
Szymon, I spoke with our developers and this request won't make it into Prism v4 as we have locked down the code for release next week. I've added it to our backlog for the next refresh of Prism next spring. However, you could implment this feature yourself as we provide the source. Have a great day, KarlAnonymous
November 03, 2010
Hi Karl, This is really great stuff. I've got a question about NavigationContext in WPF. How do you recommend passing complex context (e.g. an object) from the navigation source to the target? I have a situation where I have the object in the source and would like to pass the object to the target. However, I can't necessarily count on client-side caching, so if at all possible, I'd like to avoid forcing the target to re-get the object from the server. Thanks for your help, BrianAnonymous
November 03, 2010
Brian, The answer comes down to several factors. If the two regions share a common higher level DataContext you couild store it there. The Region also supports a RegionContext. This is an attached property that allows children of a region to store an object, then other children can access that object. You could also pass an instance of a PackageDeliveryService to each and they could access the data. Note, this is a class name I just made up, the idea is a common service could provide this functionality. Cheers, KarlAnonymous
November 03, 2010
Thanks for the quick response, Karl. I think what you're calling the "PackageDeliveryService" is what I just created and called the "BaggageClaim". Basically, the source will check a bag and get a claim ticket that gets attached to the Uri. The destination can then get the claim ticket from the Uri and claim the bag from the BaggageClaim. This approach seems to work great, and given the complexity of the app I'm dealing with, I think it is the best option of the three you provided. Always nice to have a sanity check to make sure I'm not too far off track. Thanks! BrianAnonymous
November 11, 2010
Prism v4 Region Navigation Pipeline with Unity. Currently I have problem with "view" registration. How can I define an uri which would then activate view?Anonymous
November 14, 2010
Nice work !! However does RequestNavigate let you navigate to a view in a separate Module (silverlight application) in the project? I have been trying to do this but I keep getting "Cannot create navigation target <target name>" error. It however works perfectly fine when trying to navigate to a view within the same project. I have decorated the views with the export attribute with the same name as the view.Anonymous
November 14, 2010
Can we navigate to a exported view in an another silverlight application using RequestNavigate
- Hosted under same virtual directory (same solution)
- External virtual directory If yes, then how do we do it?
Anonymous
November 18, 2010
>Can we navigate to a exported view in an another silverlight application using RequestNavigate Yes it is possible. I have list of module-view tupples. Whenever new view is requested I check whether its module is already loaded. If not I load the module and then navigate to the targeted view.Anonymous
December 13, 2010
Gan_S, Yes you can navigate to views in other assemblies in both Silverlight or WPF. The key point is you need to register the view in your container or with MEF and associate that object with a key. Then when navigating to that object, use the same key. To keep things simple, I use the full type name when registering an object and when navigating to the object. Cheers, Karl