Visually Located

XAML and GIS

Using a custom UriMapper to navigate to a login screen

When writing a Windows Phone app you might need to prompt the user with login information. Maybe you want to know who in the family is using the app, or the most likely case is that you are trying to make an app that uses web services. When you want to give the user a login screen, you want this to be the first page that they see the first time they open the app. When they have already entered login information you don’t want to show the login screen and instead take them to the main page.

There are a few ways to do this. One way that is very common is to place logic into the MainPage.xaml.cs that checks if a login is needed and if so, navigate to the login screen. When doing this you need to take care to remove any backstack entries from the NavigationService when you get to the login page. You do this because if the user hits the back button, you want them to exit the app, not go to your main page. There are two logical places to put the code for this. One in the OnNavigatedTo override and the other in the Loaded event.

override void OnNavigatedTo(NavigationEventArgs e)
{
    // Check if we have login information for the user
    if(AppSettings.HasLoginInfo == false)
    {
        MavigationService.Navigate(new Uri("/LoginPage.xaml", UriKind.Relative));
    }
    else
    {
        // Login
    }
}

This works great most of the time, but I’ve had reports of crashes from this code. I have not been able to reproduce this, and I do not know if it is device specific, but I do know that some people have experienced an application crash from the above code. The error is

Navigation is not allowed when the task is not in the foreground.
   at System.Windows.Navigation.NavigationService.Navigate(Uri source)
   at MyApplication.MainPage.OnNavigatedTo(NavigationEventArgs e)

This is a horrible first impression of the application. I never tested placing the code into the Loaded event and see how that would fair. Another common way to show a login screen is to actually have the first page be the login page. This logic works exactly as the above example but in reverse. If you have login info, go to the main page.

One thing I never liked about this was the dependency that the pages had on each other. MainPage needed to know about login information and whether to go to the login page. The second solution has better logic to it. The login page manages the login info and it determines where the user should go. But I like having the page be dumb and only handle getting the login information. So I came up with a new solution. Using a custom UriMapper.

A UriMapper is used to map one URI to another (as you probably already guessed). UriMappers are generally used when you want to map a simple URI to a more complex URI, or when you want to remove knowledge about locations of URIs (or pages).  I wanted to use a UriMapper to remove the concern of which page to initially navigate to. To do this is really simple, you’ll need to modify the WMAppManifest.xml file and add (at least) two lines to your App.xaml.cs.

In the WMAppManifest.xml, change the NavigationPage attribute of the DefaultTask element to be some random page.

<DefaultTask Name="_default" NavigationPage="LaunchPage.xaml" />

The value that you use for the NavigationPage will be used within the UriMapper class. The mapper will take LaunchPage.xaml and map it to a real page based on if the user has provided login info already.

public class LoginUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        if (uri.OriginalString == "/LaunchPage.xaml")
        {
            if (AppSettings.HasLoginInfo == false)
            {
                uri = new Uri("/LoginPage.xaml", UriKind.Relative);
            }
            else
            {
                uri = new Uri("/MainPage.xaml", UriKind.Relative);
            }
        }
        return uri;
    }
}

The mapper does not navigate to the page, it simply maps one Uri to another. A side effect of the mapper is that you can now navigate to LaunchPage.xaml. This means that from any page you can use the NavigationService.Navigate method to navigate to it. Because the mapper has already mapped LaunchPage.xaml to a real page, it will navigate to that real page. This side effect also means that if you navigate from MainPage to Page1 and push the back button, the NavigationService will attempt to navigate to LaunchPage. Everything will be fine in these cases provided that the RootFrame of your application has the custom UriMapper set.

Next you’ll need to modify the Application_Launching and Application_Activated events that your App.xaml.cs is subscribed to. I also think placing your login info here is a good place. It may not be the best, but it is better than in the MainPage or in your UriMapper. It is possible to handle logging in within the mapper as well, but that goes beyond what the mapper is suppose to do.

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    RootFrame.UriMapper = new LoginUriMapper();
 
    if (AppSettings.HasLoginInfo)
    {
        Login();
    }
}
 
private void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (e.IsApplicationInstancePreserved == false)
    {
        // tombstoned! Need to restore state
        RootFrame.UriMapper = new LoginUriMapper();
        
        if (AppSettings.NotLoggedIn)
        {
            Login();
        }
    }
}

As I mentioned above, everything will be fine provided that the application RootFrame has the UriMapper set. So it is very important to sete the mapper when the application is tombstoned. If you do not, you’ll get an error that it cannot find LaunchPage.xaml.

I’ll let you decide how to store the login information, whether or not the user has already logged in and how to login.