Updates to custom routing using the Uri Mapper in Silverlight 3

by StefanOlson 18. June 2009 14:14

Update: this code is now available for download, including a project file here.

In my previous post regarding custom URI routing using the URI mapper in Silverlight 3, I described a problem where if you wish to cancel the navigation, to display a dialog for example, returning null from the UriMapper can in some scenarios cause an exception.  After some discussions with Austin Lamb, a developer on the Silverlight team, it was clear that the solution I had chosen to cancel navigation was not going to work.

So I re-thought the way I was handling this and have come up with an alternative solution which works perfectly. 

To use the CustomUriMapper, add reference to the URI mapper as below, normally in the Frame in mainpage.xaml (replacing the default one):

<Navigation2:CustomUriMapper x:Key="uriMapper" DefaultPage="/Views/EmptyPage.xaml">
<Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
<routing:CustomUriMapper.CustomUriMappings>
<routing:AboutBoxRoute/>
<
routing:CategoryRoute/>
</
routing:CustomUriMapper.CustomUriMappings>

</
Navigation2:CustomUriMapper>

Note the new default page property, this is to provide the navigation system with a page to go to, even when canceling.  If you do not need the ability to cancel page navigation, you do not need this property. To use it just create emptypage.xaml as a blank Silverlight page class. 

If you do not need the ability to cancel page navigation - in your main page constructor, you need to tell the URI mapper about the frame in which it will be displayed:

public MainPage()
{
    InitializeComponent();
    
    (ContentFrame.UriMapper as CustomUriMapper).Frame = ContentFrame;
}

The custom routes work exactly the same as described in the previous post:

internal class CategoryRoute : CustomUriMapping
{
    public override bool MapUri(Uri uri, out Uri mappedUri)
    {
        // check for a category
        CachedCategory category = ClientStaticCache.GetCategoryByPath(uri.ToString());
        mappedUri = category != null
                        ? new Uri(string.Format("/Pages/CategoryPage.xaml?categoryid={0}", category.CategoryId),
                                  UriKind.RelativeOrAbsolute)
                        : null;
        return mappedUri != null ? true : false;
    }
}

MapUri returns a bool because there are situations where you want to return a null URI. Why would you want to do this you ask, because in some scenarios you may wish to use a URI to display a dialog, for example, an about dialog.  In this case you might have a mapping that looks like this:

internal class AboutBoxRoute: CustomUriMapping
{
    public override bool MapUri(Uri unMappedUri, out Uri mappedUri)
    {
        mappedUri = null;
        if (unMappedUri.ToString()=="About")
        {
            AboutDialog dialog = new AboutDialog();
            dialog.Show();
            return true; // we are not going to go anywhere, just let the dialog display
        }
        mappedUri = null;
        return false;
    }
}

The new CustomUriMapper is shown below.  The code is be available for download hereor in my next post, which describes the new authenticating URI mapper, a subclass of the CustomUriMapper.

/// <summary>
/// Custom URI mapper provides support for custom URI mapping using code, rather than just using regular expressions, 
/// which is the only support provided by the Microsoft URI mapper.  it also provides support for canceling navigation
/// if you wish to display a dialog when a URI is specified
/// </summary>
/// <example>
/// There are two parts to using the custom URI mapper. Firstly, you declare the URI mapper and app.xaml as you would the
/// normal Microsoft mapper. If you wish to be able to cancel navigation, you can set a default page, in app.xaml. Secondly, 
/// if you wish to be able to cancel navigation to display a dialog or anything else, you need to set the frame.
/// 
/// In app.xaml:
/// <code>
/// <routing:CustomUriMapper x:Key="uriMapper" DefaultPage="/Views/EmptyPage.xaml">
//      <Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
//  </routing:CustomUriMapper>
/// </code>
/// 
/// To set the frame, probably in the main window.
/// <code>
/// (App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
/// </code>
/// 
/// </example>
[ContentProperty("UriMappings")]
public class CustomUriMapper : UriMapperBase
{
    // private variables
    private bool _cancelNavigation;

    /// <summary>
    /// Gets or sets a list of UriMapping objects.
    /// </summary>
    public Collection<UriMapping> UriMappings { get; private set; }
    /// <summary>
    /// Gets or sets a list of CustomUriMappings objects.
    /// </summary>
    public Collection<CustomUriMapping> CustomUriMappings { get; private set; }

    /// <summary>
    /// specifies the default page. This is required because even when canceling navigation, the Microsoft URI mapper
    /// needs to provide a page to map to. To use this create a blank page and reference it.
    /// </summary>
    public Uri DefaultPage { get; set; }

    private Frame _frame;
    /// <summary>
    /// specifies the frame in which navigation for this URI mapper will occur. If you want to be able to cancel 
    /// navigation, this frame needs to be set.
    /// </summary>
    /// <example>
    /// To set the frame, probably in the main window.
    /// <code>
    /// (App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
    /// </code>
    /// </example>
    public Frame Frame
    {
        get { return _frame; }
        set
        {
            _frame = value;
            _frame.Navigating += FrameNavigating;
        }
    }

    public CustomUriMapper()
    {
        UriMappings = new Collection<UriMapping>();
        CustomUriMappings = new Collection<CustomUriMapping>();
    }
    
    /// <summary>
    /// Maps a given URI and returns a mapped URI.
    /// </summary>
    /// <param name="uri">Original URI value to be mapped to a new URI.</param>
    /// <returns>A URI derived from the <paramref name="uri"/> parameter.</returns>
    public override Uri MapUri(Uri uri)
    {
        _cancelNavigation = false;

        Collection<UriMapping> uriMappings = UriMappings;
        if (uriMappings == null)
        {
            throw new InvalidOperationException("MustNotHaveANullUriMappingCollection");
        }
        foreach (UriMapping mapping in uriMappings)
        {
            Uri uri2 = mapping.MapUri(uri);
            if (uri2 != null)
            {
                return CheckCanNavigateToUri(uri, uri2);
            }
        }

        foreach (CustomUriMapping mapping in CustomUriMappings)
        {
            Uri uri2;
            if (!mapping.MapUri(uri, out uri2)) continue;
            if (uri2 == null && Frame!=null)
            {
                return CancelNavigation();
            }
            return CheckCanNavigateToUri(uri, uri2);
        }
        // now nothing...
        return CheckCanNavigateToUri(uri, uri); 
    }

    /// <summary>
    /// cancels navigation to the current URI.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// thrown if there is no default page
    /// </exception>
    /// <returns>the new URI to navigate to, which will be the default page. This is required by 
    /// the Microsoft navigation system</returns>
    protected Uri CancelNavigation()
    {
        // if there is no URI to go to, we will cancel the navigation at the next possible opportunity
        _cancelNavigation = true;
        if (DefaultPage==null)
        {
            throw new InvalidOperationException("must have a default page");
        }
        // we need to return a valid URI, that points to the actual page, because the cancel is not 
        // checked until after the validity of the URI is checked
        return DefaultPage;
    }

    /// <summary>
    /// this function is called before any URI is returned. This gives you the opportunity to do any custom work, 
    /// such as authentication, prior to the URI being returned to the navigation system
    /// </summary>
    /// <param name="unmappedUri">the URI that was provided by the navigation system, before it was mapped</param>
    /// <param name="mappedUri">the URI, after mapping</param>
    /// <returns>the URI to navigate to, by default this is the same as the mapped URI</returns>
    protected virtual Uri CheckCanNavigateToUri(Uri unmappedUri, Uri mappedUri)
    {
        return mappedUri;
    }

    void FrameNavigating(object sender, NavigatingCancelEventArgs e)
    {
        e.Cancel = _cancelNavigation;
    }
}
…Stefan

Tags:

Silverlight

Comments

8/10/2009 3:06:04 AM #

Pingback from wuil.api.li

Wuil  » Soul Solutions Blog – Virtual Earth Silverlight Thematic Overlay with …

wuil.api.li

Add comment




biuquote
  • Comment
  • Preview
Loading



About the author

Stefan Olson is the Managing Director of Olson Software.  He has been developing software using Microsoft Technologies for nearly 20 years.

He is currently working on building the next generation Virtual Tour software in WPF and Silverlight for www.palacevirtualtours.com.

Tag cloud