Azure + Silverlight 4 + RIA Services + MVC2 (Part 4)

4 minute read

by Peter Daukintis

So, now I know how to set up a basic project, store and retrieve my data in Azure Table storage and have incorporated the asp.net providers that use Table Storage it’s time to turn my attention towards finding the right architecture to make the most out of RIA Services. I would like to use the PRISM libraries with an MVVM approach but I’m not sure how well these play with RIA Services in a Silverlight Business Application.

The first step was to get a version of PRISM that works with vs2010 – this I found here http://blogs.southworks.net/dschenkelman/2009/11/09/prism-2-composite-application-guidance-for-wpf-silverlight-migrated-to-visual-studio-2010-beta-2/

Then I imagined that the solution would consist of using PRISM’s module support to load each tab of the Silverlight Business Application on demand. This should support only downloading xap’s that the user has elected to view. I wasn’t searching for a fully dynamic solution – just a middle ground where the application is split up into separate modules – and the right environment in which to improve the design later.

The solution I went for was to create hyperlinks and related xaml files which fit with the Silverlight 4 navigation scenario but to define a PRISM ‘region’ inside each navigation page. If you are not familiar with PRISM regions or in general I would suggest that you take a look at Mike Taulty’s series here http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2009/10/27/prism-and-silverlight-screencasts-on-channel-9.aspx. The regions will act as a placeholder for the user interface which will later be supplied by the modules. So, effectively each tab is hard-coded and it’s view defines a region to be filled in later.

Here’s an example of one of the xaml pages:

<navigation:Page x:Class="PrismNav.Views.AudioModule"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           mc:Ignorable="d"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           xmlns:Regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation" 
           d:DesignWidth="640" d:DesignHeight="480"
           Title="Audio Module">
  <Grid x:Name="LayoutRoot">
    <ItemsControl x:Name="Module2Region" Regions:RegionManager.RegionName="Module2Region"/>
  </Grid>
</navigation:Page>

and here’s an example of the link in the main xaml file:

                        <Rectangle x:Name="Divider3" Style="{StaticResource DividerStyle}"/>

                        <HyperlinkButton x:Name="AudioModuleLink" Style="{StaticResource LinkStyle}" 
                                     NavigateUri="/AudioModule" TargetName="ContentFrame" Content="Audio Module"/>

One of these are added for each required tab.

Some extra steps are required to initialise the PRISM environment:

Bootstrapper – add a bootstrapper class to the project to let PRISM know about the main window and also to intialise a module catalog.

    public class BootStrapper : UnityBootstrapper
    {
        protected override DependencyObject CreateShell()
        {
            Shell shell = Container.Resolve<Shell>(); 
            Application.Current.RootVisual = shell; 
            return shell;
        }

        protected override IModuleCatalog GetModuleCatalog()
        {
            ModuleCatalog moduleCatalog = ModuleCatalog.CreateFromXaml(new Uri("PrismNav;component/ModuleCatalog.xaml", 
             UriKind.Relative));
            return moduleCatalog;
        }
    }
}

Where the Shell is the main page of the Silverlight app renamed to Shell.xaml (to adhere to convention). It is also necessary to kick off a call to run the bootstrapper code – this can be done like this

            var bootStrapper = new BootStrapper();
            bootStrapper.Run();
            _unityContainer = bootStrapper.Container;

inserted into the Application_Startup method in App.xaml.cs (Note that I take a reference to the bootstrapper’s unity container here – this I use to resolve creation of the Shell view class in InitializeRootVisual() as in the generated code it uses new Shell() with no ctor parameters but I have modified my Shell class to be constructor injected with the IModuleManager interface).

The module catalog you can add to the app and it’s contents are like this:

m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                 xmlns:m="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">
    <m:ModuleInfoGroup Ref="HomeModule.xap" InitializationMode="OnDemand">
        <m:ModuleInfo ModuleName="HomeModule.InitModule"
                      ModuleType="HomeModule.InitModule, HomeModule, Version=1.0.0.0"/>
    </m:ModuleInfoGroup>
    <m:ModuleInfoGroup Ref="AudioModule.xap" InitializationMode="OnDemand">
        <m:ModuleInfo ModuleName="AudioModule.InitModule"
                      ModuleType="AudioModule.InitModule, AudioModule, Version=1.0.0.0"/>
    </m:ModuleInfoGroup>
</m:ModuleCatalog>

This describes where the code for the modules are and some extra settings describing how the module should be loaded, etc..Now, onto creating a module:

using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;

namespace AudioModule
{
    public class InitModule : IModule
    {
        private readonly IRegionManager _regionManager;

        public InitModule(IRegionManager regionManager)
        {
            _regionManager = regionManager;
        }

        #region IModule Members

        public void Initialize()
        {
            _regionManager.RegisterViewWithRegion("Module2Region", typeof(Audio));
        }

        #endregion
    }
}

Add a new Silverlight application to the solution and empty out it’s contents and add in the code above which provides the PRISM module definition. Add a xaml file which will be the ui you wish to be injected into the region and the Initialise call above registers this modules interest in the ‘module2Region’.

So, now we have two ends of the story – these need to be connected together and here’s how to do it.

When the tabbed hyperlinks are clicked on a Silverlight Business Application there is some code in the code behind for the main shell file which is triggered – see the ContentFrame_Navigated method. This is just used to set the states of the hyperlink controls but is a handy place to load the modules. This can be done as below:

        private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
        {
            string blah = e.Uri.ToString();
            if (blah.EndsWith("Module"))
            {
                blah = blah.Remove(0, 1);
                _moduleManager.LoadModule(blah + ".InitModule");
            }

            foreach (UIElement child in LinksStackPanel.Children)
            {
                HyperlinkButton hb = child as HyperlinkButton;
                if (hb != null && hb.NavigateUri != null)
                {
                    if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
                    {
                        VisualStateManager.GoToState(hb, "ActiveLink", true);
                    }
                    else
                    {
                        VisualStateManager.GoToState(hb, "InactiveLink", true);
                    }
                }
            }
        }

Please note that I have just used a ‘quick and dirty’ naming convention to get the module name from the Uri – this obviously doesn’t scale too well – but will do for now!

And that’s it – although it’s not perfect it is the start of a modular architecture. In my next post I will investigate how to have the RIA Services code play nicely with this modular architecture and allow it to generate my client side service calls + entities. 

Comments