Handling NavigationFailed

Topics: Prism v4 - WPF 4
Apr 27, 2012 at 9:45 AM

Do you have an example application with your recommended approach for Handling NavigationFailed, i.e. exceptions thrown in the Navigation process?

My problem is that I am using the INavigationAware interface, and based on parameters passed through to the ViewModel I want to get data from the database. Any errors in this process, shouldn't really be being handled by the navigation process, as I don't think it has much to do with navigation problems, as the actual navigation still completes. For example if I am unable to connect to the database, this isn't a navigation issue, and should be handled differently.

I think the try catch on RequestNavigate should only really catch RegionNavigation exception's, not all Exception's. You should know when navigation errors are likely to occur, so throw navigationexceptions, anything you are not expecting should be picked up by the application's unhandled exceptions handler.

Developer
Apr 27, 2012 at 7:24 PM
Edited Apr 27, 2012 at 7:57 PM

Hi,

Based on my understanding, when an exception is raised during a navigation request the exception will be cached by the RegionNavigationService as you mentioned. However, when a navigation request fails, the NavigationFailed event will also be raised and the RegionNavigationFailedEventArgs that will be passed through this event will contain the aforementioned exception in its Error property. Therefore, a possible approach could be to subscribe to the NavigationFailed event before performing the NavigationRequest in order to receive any possible exceptions through that event.

You can find more information about this in the following blog post:

Also, if you believe that this behavior should be changed so that the exceptions are not swallowed, you can vote in the following work item so that the team can analyze this request for future releases:

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

May 4, 2012 at 12:09 PM

But if you were to subscribe to this failed event? How exactly would you recommend implementing this? Do you have a sample app?

Developer
May 4, 2012 at 6:12 PM


Hi James,

You could find more information about this and a possible example on how to subscribe to the NavigationFailed Event in Karl Shifflett's Prism v4 Region Navigation Pipeline. (Region.NavigationService.NavigationFailed Event section).

The sample application provided in this blog post implements this approach. Particularly you could check how the NavigationFailed Event is subscribed and handled in the ShellView code behind.

Also, another approach to retrieve any exception that was thrown during navigation could be using the third optional argument in the RequestNavigate method, which allows to pass an Action<NavigationResult> callback. This delegate will be called at the end of the navigation request process, passing the result of the navigation request. The NavigationResult class defines properties that provide information about the navigation operation. The Result property indicates whether or not navigation succeeded. If navigation failed, the Error property provides a reference to any exception that was thrown during navigation, this is also explained in the aforementioned blog post.

I hope you find this handy,

Agustin Adami
http://blogs.southworks.net/aadami

May 8, 2012 at 4:13 PM

Thanks for getting back to me.

So for the NavigationFailed event, I would need to hookup events on a region by region basis? In my application I have a lot of different regions, and contextual regions. This doesn't seem to lend itself too well. I understand if you have one region and navigating using the navigationservice of that region. But I don't use that the majority of the time, I am often navigating to various different regions.

Or I would have to do it on each navigation and declare a callback? My worry with this is, that with a few developers working on the project, they may well forget to add a callback, then suddenly errors are getting hidden.

Ideally I would just have one hookup of the event in like the shell and handle it somewhere centrally. Is this not possible?

I'm not sure what's best to do. I'm a bit worried that in either of these scenario's that exceptions will get hidden as someone has forgotten to wire something up. Anything that we have forgotten to handle should really be handled by the unhandled exception manager, therefore alerting us to do something with it.

Developer
May 8, 2012 at 6:45 PM
Edited May 8, 2012 at 6:47 PM

Hi,

Based on my understanding, a possible approach for handling the NavigationFailed event centrally for example in your shell like you mentioned, could be by subscribing to the Regions.CollectionChanged event, so that when a new region is added you could subscribe your centralize handler to the NavigationFailed event of this new region.

For example the code in you ShellViewModel could look like in the following code snippet:

 

public ShellViewModel(IRegionManager regionManager)
{
            _regionManager = regionManager;

            (...)

            _regionManager.Regions.CollectionChanged += Regions_CollectionChanged;
}

(...)

void Regions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
            if(e.Action == NotifyCollectionChangedAction.Add) {
                var list = e.NewItems;
                foreach(var item in list) {
                    var region = item as Region;
                    region.NavigationService.NavigationFailed += NavigationService_NavigationFailed;
                }
            }
}

 

Regards,

Agustin Adami
http://blogs.southworks.net/aadami

May 9, 2012 at 9:13 AM

I would also need to somehow do this with my scoped region manager's wouldn't I? As I have multiple instances of a region within a PatientView.

May 9, 2012 at 9:39 AM

So... Presumably I would just have to do this where ever I have a scoped Region Manager?

Developer
May 9, 2012 at 6:46 PM

Hi James,

As you mentioned, following the approach proposed above, you would have to subscribe to the CollectionsChanged event of the Region collection of each RegionManager you are using in your application. How to do this will depend mostly on your personal preferences and the requirements of your scenario.

As a possible approach to extrapolate the aforementioned approach for multiple region managers, you could create a simple Shared Service which could subscribe to the CollectionChanged event of each region manager and expose an event which could be raised when a navigation request in any Region fails. For example, each time you inject a view with a scoped region manager, you pass that region manager to the service so that it will subscribe to its CollectionChanged event like in the above code snippet (it is not necessary to store the RegionManager inside the service, only the subscription.) Then, the Shell could subscribe to the event exposed by this service and when a navigation request fails the Shell will be notified of this through this event.

You can find more information about Shared Services in the following section of the documentation:

Damian Cherubini
http://blogs.southworks.net/dcherubini

May 10, 2012 at 8:46 AM

Cool yep thanks for all your help.

Feb 25, 2014 at 11:45 AM
Anyway you could set a flag of some sort to throw these error's normally? It has turned into a bit of a mess in my application wiring and un-wiring these events everywhere as it has grown more complex.

In 4.2 perhaps??
Feb 25, 2014 at 6:40 PM
Hi James,

Based on my understanding Prism 4.2 would not have changed this behaviour yet. as the following download site describes the list of updates performed.

However, the following item related to this issue is open so you could vote for making the P&P team fix it and update the library on a future release:

Anyway, you could always be able to modify the Prism Library and make the ExecuteNavigation() actually throws the exceptions as Guido Maliandi tells in his post.

Regards.

Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky
Feb 26, 2014 at 8:37 AM
Thank you!

It appears I must have already voted on this unfortunately and not remembered.

How would I go about modifying the library? Any chance of an example?
Feb 26, 2014 at 5:17 PM
Hi James,

As Guido Maliandi describes in his post linked above, you could modify the Catch block inside the ExecuteNavigation() method at RegionNavigationService. Therefore, you could rethrow the exception on the catch block after the NotifyNavigationFailed() call so the exception could be handled upwards on the call stack.

Bearing in mind that you would modify a service class, there are 2 alternatives for doing this:

  1. Instead of adding all Prism Library assemblies, you could add the project Prism.Desktop.csproj to your solution instead, and then modify the RegionNavigationService class.
  2. Create a CustomRegionNavigationService class with the modification explained above, and then register this service on the BootStrapper before registering the default one. The ConfigureContainer() method would look like as follows:
 protected override void ConfigureContainer()
 {
      RegisterTypeIfMissing(typeof(IRegionNavigationService), typeof(CustomRegionNavigationService), false);
       
      base.ConfigureContainer();
 }
I hope this helped you,
Regards.

Gabriel Ostrowsky.
https://blogs.southworks.net/gostrowsky
Feb 28, 2014 at 8:42 AM
Cool thanks, so I've created a CustomRegionNavigationService and changed the following. Is this correct?
    private void ExecuteNavigation(NavigationContext navigationContext, object[] activeViews, Action<NavigationResult> navigationCallback)
    {
        try
        {
            NotifyActiveViewsNavigatingFrom(navigationContext, activeViews);

            Tuple<object, IRegionManager> tuple = this.regionNavigationContentLoader.LoadContent(this.Region, navigationContext);

            // Raise the navigating event just before activing the view.
            this.RaiseNavigating(navigationContext);

            this.Region.Activate(tuple.Item1);

            // Update the navigation journal before notifying others of navigaton
            IRegionNavigationJournalEntry journalEntry = this.serviceLocator.GetInstance<IRegionNavigationJournalEntry>();
            journalEntry.Uri = navigationContext.Uri;
            this.journal.RecordNavigation(journalEntry);

            // The view can be informed of navigation
            InvokeOnNavigationAwareElement(tuple.Item1, (n) => n.OnNavigatedTo(navigationContext));

            navigationCallback(new CustomNavigationResult(navigationContext, true, tuple.Item2));

            // Raise the navigated event when navigation is completed.
            this.RaiseNavigated(navigationContext);
        }
        catch (Exception e)
        {
            this.NotifyNavigationFailed(navigationContext, navigationCallback, e);
            throw e;
        }
    }
and
    public void RequestNavigate(Uri target, Action<NavigationResult> navigationCallback)
    {
        if (navigationCallback == null) throw new ArgumentNullException("navigationCallback");

        try
        {
            this.DoNavigate(target, navigationCallback);
        }
        catch (Exception e)
        {
            this.NotifyNavigationFailed(new NavigationContext(this, target), navigationCallback, e);
            throw e;
        }
    }
Feb 28, 2014 at 3:13 PM
Hi James,

The changes you mentioned would be correct. That way, the RequestNavigate() method would throw the exception in order to let you handle it on the method that calls the Navigation.

Nevertheless, I would like to point out to you that there are two different alternatives of rethrowing the exception and you may want to consider the best option for your implementation:

  • throw e; would make the stack trace reset to the current catch location, which could be significantly different from where the original exception was thrown. It is considered as a CA violation.
  • throw; however, would preserve the original stack trace so you would be able to properly debug the issue.
You can find more information on the following MSDN links:

Regards.
Gabriel Ostrowsky.
https://blogs.southworks.net/gostrowsky
Feb 28, 2014 at 5:15 PM
I see! I didn't know that no. Thanks for your help!