Introduction

This will be a pretty short and sweet blog post, but the issue that I describe caused me enough headaches, and was hard enough to ‘Google’ that adding more ways to get there is in and of itself worth it.  The problem I ran into was with a Silverlight 4 RIA Services application which utilizes Windows Authentication and needs to run as an out of browser application.  The application needed to get detailed user information in order to properly construct the initial application user interface.  The problem is that the Authentication Service call was timing out whenever I ran the application, and my RIA Service was never even executed.  I’ve tried to trim down the example to the bare minimum, but I decided to stop trimming at the point where I actually removed the RIA service entirely Smile  So, although the issue does not require RIA Services to reproduce, it will often be encountered when implementing a RIA Service application.

 

The Problem

The timeout problem we were seeing was only occurring when the application was running out of the browser.  The problem was complicated by the fact that the NetworkInterface.GetIsNetworkAvailable() routine was returning ‘true’, indicating that a network connection was available, but all of the network access that we tested would result in a timeout.  There did not appear to be anything causing this timeout, and I was at an impasse until I stumbled upon this forum post which mentioned something about the RootVisual of the application.  I’m not certain how they discovered the RootVisual link, but that is the key.

 

Example Code

The following code snippet outlines an example of the original code:

    public partial class App : Application
    {

        public App()
        {
            this.Startup += this.Application_Startup;
            this.Exit += this.Application_Exit;
            this.UnhandledException += this.Application_UnhandledException;

            WebContext context = new WebContext();
            context.Authentication = new WindowsAuthentication();
            this.ApplicationLifetimeObjects.Add(context);

            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.Resources.Add("WebContext", WebContext.Current);
            this.Resources.Add("Role-Administrator", RoleConstants.Administrator);
            this.Resources.Add("Role-Flunky", RoleConstants.Flunky);

            if (!NetworkInterface.GetIsNetworkAvailable())
            {
                MessageBox.Show("Here I am before RootVisual is set and the network isn't available.");
                return;
            }
            else
            {
                MessageBox.Show("Currently showing as network access is enabled...");
            }

            // if we're running out of browser, then use the ClientHttp stack...
            if (App.Current.IsRunningOutOfBrowser)
            {
                HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
                HttpWebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);

                App.Current.CheckAndDownloadUpdateCompleted += (s, args) =>
                {
                    if (args.UpdateAvailable)
                    {
                        MessageBox.Show("An update has been downloaded. " +
                            "Restart the application to run the new version.");
                    }
                    else if (args.Error != null &&
                             args.Error is PlatformNotSupportedException)
                    {
                        MessageBox.Show("An application update is available, " +
                            "but it requires a new version of Silverlight. " +
                            "Visit the application home page to upgrade.");
                    }
                };
                App.Current.CheckAndDownloadUpdateAsync();
            }

            WebContext.Current.Authentication.LoadUser(
                (loadOp) =>
                {
                    if (!loadOp.HasError)
                    {
                        AppContext.CurrentUser = loadOp.User as CustomPrincipal;

                        // setup the 'default' context
                        AppContext.ApplicationUser = new UserContext()
                        {
                            UserId = AppContext.CurrentUser.CustomIdentity.UserID,
                            HasReadAccess = true,
                            HasWriteAccess = AppContext.CurrentUser.IsInRole(RoleConstants.Administrator)
                        };

                        ///***NOTE: This bit works fine in browser, but never gets executed OOB
                        AppContext.RootView = new MainPage();
                        this.RootVisual = AppContext.RootView;

                        // now load the official context from the server
                        SilverlightRIAServicesOOBDomainContext context = new SilverlightRIAServicesOOBDomainContext();
                        context.Load(
                            context.GetUserByIdQuery(AppContext.ApplicationUser.UserId),
                            LoadBehavior.MergeIntoCurrent,
                            (userOp) =>
                            {
                                if (!userOp.HasError)
                                {
                                    AppContext.ApplicationUser.Identity = userOp.Entities.FirstOrDefault();
                                    AppContext.ApplicationUser.Role = AppContext.ApplicationUser.Identity.Role;
                                }
                                else
                                {
                                    MessageBox.Show(ErrorMessages.ExceptionMessage(userOp.Error));
                                }
                            },
                            null);

                    }
                    else
                    {
                        MessageBox.Show(ErrorMessages.ExceptionMessage(loadOp.Error));
                    }
                },
                null);

        }
    }

 

As you can see above, this is fairly standard code.  The App constructor handles creating the WebContext instance and assigning the WindowsAuthentication class to automatically use the current logged in user to authenticate with the RIA Service.  Then, when the application starts up, the AuthenticationService::LoadUser method is called to pull down detailed user principal/identity information to do things like indicate who is currently logged in, control access to portions of the UI based on user role(s), etc.  In this case, after the LoadUser call, there’s a RIA Service call to get detailed information about the user that isn’t included in the IPrincipal/IIdentity implementation.

When you run this code inside the browser, everything functions as expected.  However, once you install the application out of browser, the initial LoadUser service call times out and the application remains at the ‘white screen’ due to the RootVisual not being set to our primary application view.

 

The Solution

The solution, as mentioned in the forum post linked above, is to initialize the Root Visual to SOMETHING prior to any service calls or network access.  In this case, I didn’t want my view to be initialized immediately, because it required details about the current user [e.g. where to navigate, what controls should appear, etc] and there really wasn’t any purpose served by showing a ‘loading’ screen or some such.  So, I simply assigned a ‘Grid’ to the RootVisual, and then assigned my actual RootView as the first [and only] child of the grid.  The fixed code looks like this:

    public partial class App : Application
    {

        public App()
        {
            this.Startup += this.Application_Startup;
            this.Exit += this.Application_Exit;
            this.UnhandledException += this.Application_UnhandledException;

            WebContext context = new WebContext();
            context.Authentication = new WindowsAuthentication();
            this.ApplicationLifetimeObjects.Add(context);

            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            this.Resources.Add("WebContext", WebContext.Current);
            this.Resources.Add("Role-Administrator", RoleConstants.Administrator);
            this.Resources.Add("Role-Flunky", RoleConstants.Flunky);

            if (!NetworkInterface.GetIsNetworkAvailable())
            {
                MessageBox.Show("Here I am before RootVisual is set and the network isn't available.");
                return;
            }
            else
            {
                MessageBox.Show("Currently showing as network access is enabled...");
            }

            ///***NOTE:  This bit fixes the OOB problems...
            this.RootVisual = new Grid();

            if (!NetworkInterface.GetIsNetworkAvailable())
            {
                MessageBox.Show("Here I am after RootVisual is set and the network isn't available.");
                return;
            }

            // if we're running out of browser, then use the ClientHttp stack...
            if (App.Current.IsRunningOutOfBrowser)
            {
                HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
                HttpWebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);

                App.Current.CheckAndDownloadUpdateCompleted += (s, args) =>
                {
                    if (args.UpdateAvailable)
                    {
                        MessageBox.Show("An update has been downloaded. " +
                            "Restart the application to run the new version.");
                    }
                    else if (args.Error != null &&
                             args.Error is PlatformNotSupportedException)
                    {
                        MessageBox.Show("An application update is available, " +
                            "but it requires a new version of Silverlight. " +
                            "Visit the application home page to upgrade.");
                    }
                };
                App.Current.CheckAndDownloadUpdateAsync();
            }

            WebContext.Current.Authentication.LoadUser(
                (loadOp) =>
                {
                    if (!loadOp.HasError)
                    {
                        AppContext.CurrentUser = loadOp.User as CustomPrincipal;

                        // setup the 'default' context
                        AppContext.ApplicationUser = new UserContext()
                        {
                            UserId = AppContext.CurrentUser.CustomIdentity.UserID,
                            HasReadAccess = true,
                            HasWriteAccess = AppContext.CurrentUser.IsInRole(RoleConstants.Administrator)
                        };

                        ///***NOTE: This bit is now fixed since RootVisual can't be set twice
                        AppContext.RootView = new MainPage();
                        ((Grid)this.RootVisual).Children.Add(AppContext.RootView);

                        // now load the official context from the server
                        SilverlightRIAServicesOOBDomainContext context = new SilverlightRIAServicesOOBDomainContext();
                        context.Load(
                            context.GetUserByIdQuery(AppContext.ApplicationUser.UserId),
                            LoadBehavior.MergeIntoCurrent,
                            (userOp) =>
                            {
                                if (!userOp.HasError)
                                {
                                    AppContext.ApplicationUser.Identity = userOp.Entities.FirstOrDefault();
                                    AppContext.ApplicationUser.Role = AppContext.ApplicationUser.Identity.Role;
                                }
                                else
                                {
                                    MessageBox.Show(ErrorMessages.ExceptionMessage(userOp.Error));
                                }
                            },
                            null);

                    }
                    else
                    {
                        MessageBox.Show(ErrorMessages.ExceptionMessage(loadOp.Error));
                    }
                },
                null);

        }

    }

 

This simple fix enables the application to work properly both in and out of browser, however, there is a secondary side effect if you’re using ChildWindow(s) for dialogs.  The ChildWindow has a nasty bug in that it intermittently decides to not re-enable your underlying controls when the ChildWindow is closed.  The accepted workaround for this is to set the Control.IsEnabled dependency property to ‘true’ on the RootVisual in the Closing event of the ChildWindow.  Unfortunately, when the RootVisual is a Grid, doing so causes an ‘Unexpected Error’ unhandled exception to be thrown by the Silverlight runtime and things get a little weird.  To get around this, we store our ‘actual’ root visual in a static class and then just set the IsEnabled property on that static variable.  This results in the same [correct] behavior as before and the exception is averted.

 

Final Thoughts

It would be nice if this wasn’t an issue at all, and I’m uncertain WHY the RootVisual needs to be set prior to any networking calls, but so long as that remains the case, the NetworkInterface.GetIsNetworkAvailable() method should return ‘false’ until the RootVisual is set properly.  Again, why the network access is disabled is a mystery, and feels like a pretty large bug.  In any case, if you run into this problem like we did, hopefully you can avoid banging your head against the wall as we did trying to find the solution.  The solution we’ve come up with won’t work for everyone, but hopefully it’ll get you started in the right direction.

 

Sample Code:  SilverlightRIAServiceOOB-20110410.zip (226 kB)