Architecting Silverlight LOB applications (Part 5) – Modules and UI composition


In this fifth post and the next ones I’m going to write about client development and how to create a modular and composite silverlight application. In this post I’ll focus on UI composition.

When building composite LOB silverlight apps you should be aware that modularity is an important requirement. As a developer it should be easier for you to mantain your code and add new features to your application without having to change the rest, plus your application’s XAP file doesn’t grow bigger. Every module is a separate XAP file, that can be downloaded on demand, depending on the user’s intent to use a certain functionallity.

Below is one possible architecture to achieve what we need.

solution

The Shell folder contains the main xap, which I called MyApp. This xap is only responsible for downloading others and provide means for them to display its content inside the main application. The Modules folder contains 4 essential modules for the business scenario I’ve defined in a previous post. The Orders module allows non-employee users to fullfil new orders. The Sales module allows employees to view pending orders and process them. The statistics module allows employees to view charts and reports of sales evolution. Features for human resources department are accomplished by the Administration module, which also should allow application users to be created and/or managed. Modules and the Shell are compiled to MyApp.Web’s ClientBin folder.

The Infrastructure project is a key piece. It defines ways to communicate between modules (modules don’t reference other modules) and between the main application. In other words, it is a common contract shared by the modules. When it is defined or partially defined, module development can start. Notice that the development of the Orders module doesn’t depend on the development of the Sales module or the Statistics module, which means that different people can work on each one.

MyApp.Data.Silverlight is a project that should have WCF RIA Services generated code, plus other service clients which are used by modules.

The Server layer corresponds to the architecture I’ve defined in the first post.

So how does our Shell download the modules? We just need to specify a module catalog and give it to PRISM’s Bootstrapper which is responsible for initializing PRISM’s framework. Below is the module catalog I’ve defined for this scenario.

<prism:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:prism="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">

    <prism:ModuleInfo Ref="MyApp.Modules.Administration.xap"
                      ModuleName="Administration"
                      ModuleType="MyApp.Modules.Administration.Module, MyApp.Modules.Administration, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>

    <prism:ModuleInfo Ref="MyApp.Modules.Statistics.xap"
                      ModuleName="Statistics"
                      ModuleType="MyApp.Modules.Statistics.Module, MyApp.Modules.Statistics, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>

    <prism:ModuleInfo Ref="MyApp.Modules.Sales.xap"
                      ModuleName="Sales"
                      ModuleType="MyApp.Modules.Sales.Module, MyApp.Modules.Sales, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>

    <prism:ModuleInfo Ref="MyApp.Modules.Orders.xap"
                      ModuleName="Orders"
                      ModuleType="MyApp.Modules.Orders.Module, MyApp.Modules.Orders, Version=1.0.0.0"
                      InitializationMode="WhenAvailable">
    </prism:ModuleInfo>
</prism:ModuleCatalog>

Notice the InitializationMode attributes. It allows us to specify wether the modules should be downloaded as soon as possible or on demand. For now, I want my modules to download as soon as possible but later I’ll show effective techniques to download them on demand. However, that doesn’t affect the way I build them.

To start, our Shell can have a top bar displaying a hello message, a menu on the left and a region where modules can display its content.

    <Border BorderThickness="5" CornerRadius="10">
        <tlk:DockPanel LastChildFill="True">
            <shellWelcome:WelcomeBar tlk:DockPanel.Dock="Top" Margin="5"/>
            <shellMenu:Menu tlk:DockPanel.Dock="Left" VerticalAlignment="Stretch" Margin="5,0,5,5"/>
            <shellContainer:MainContainer Margin="0,0,5,5"/>
        </tlk:DockPanel>
    </Border>

The visual result is something like this:

shell

I’d like the menu and the main container to be dynamic, in other words, have its content set dinamically by modules instead of being defined in the controls itself.

We could create an interface like IMenu, implemented by our Menu’s viewmodel and use it by modules to display content. While that seems to be a decoupled aproach because your modules can display its content without depending on the menu control itself, it is actually coupled to the object that will implement the IMenu interface. That means that if you want to display your menu options in other places, like a toolbar or a ribbon bar, you will need another interface to interact with those controls. A solution to this problem is to use a message publisher/subscriber technique relying on a mediator object, that is PRISM’s EventAggregator. Now what you do is publishing a message saying “I want to display this menu option” and which ever control that is interested in showing menu options subscribes that message and acts accordingly. Lets see how we can do that:

public class MenuOptionMessage : CompositePresentationEvent<MenuOptionMessageArgs> { }

public class MenuOptionMessageArgs
{
    public ICommand Command { get; set; }
    public string Text { get; set; }
    public string ImageSourceUri { get; set; }

    public string Group { get; set; }
}

The MenuOptionMessage is a PRISM event whose payload is a MenuOptionMessageArgs message. So whenever you want to publish a MenuOptionMessage you create a MenuOptionMessageArgs and tell the EventAggregator to broadcast it to every subscriber. The EventAggregator can be accessed through dependency injection or the IoC container. I usually create a simple utility class to keep the code cleaner:

public class Messenger
{
    public static T Get<T>()
        where T : EventBase
    {
        var ev = ServiceLocator.Current.GetInstance<IEventAggregator>();
        return ev.GetEvent<T>();
    }
}

Now we can have our modules publish MenuOptionMessages.

Order Module initialization:

Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.NewOrderCommand,
    Text = "Create New Order",
    Group = "Orders"
});
Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewOrdersCommand,
    Text = "View Orders",
    Group = "Orders"
});

Sales module initialization:

Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewPendingOrdersCommand,
    Text = "Pending Orders",
    Group = "Sales"
});
Messenger.Get<MenuOptionMessage>().Publish(new MenuOptionMessageArgs
{
    Command = Commands.ViewProcessedOrdersCommand,
    Text = "Processed Orders",
    Group = "Sales"
});

The Menu control displays every command grouped by its Group and creates an expander for each Group. You can access the source code to confirm that.

Now what we need to do is have our commands display views.

PRISM has a region concept, where you define regions within your Shell, give them a name and then your modules inject views into those regions by specifying the name. I’m no fan of this feature because it creates tight coupling between views and regions. Besides this means that your screen logic only works with PRISM. I usually use a different approach, which allows us to also use PRISM regions but other UI frameworks as well. Besides, it is not that hard to build your own UI framework, if you keep it simple. For example, instead of thinking on regions, think about view positions.

public enum ViewPosition
{
    Left,
    Right,
    Top,
    Bottom,
    Center,
    Floating
}

Using the same technique we did for the Menu, whenever we want to display a view, we simply have to publish a message where we specify the position and the view itself and whoever is responsible to display that view in the specified position should subscribe the message and act accordingly. These messages should be defined in the Infastructure assembly. Lets see how the message is defined:

public class CreateViewMessage : CompositePresentationEvent<CreateViewMessageArgs> { }

public class CreateViewMessageArgs
{
    public Lazy<object> View { get; set; }
    public ViewPosition Position { get; set; }
    public string Title { get; set; } //optional
}

Notice the Lazy object there. That allows you to have your view created by the subscriber instead of being created by the publisher. That allows you to use different threads when publishing/subscribing, although in this case the subscriber should use the UI thread.

Below is an example of how to publish a CreateViewMessage from the Order module.

public class Commands
{
    public static ICommand NewOrderCommand = new ActionCommand(Commands.NewOrder);
    public static ICommand ViewOrdersCommand = new ActionCommand(Commands.ViewOrders);

    private static void NewOrder()
    {
        Messenger.Get<CreateViewMessage>().Publish(new CreateViewMessageArgs
        {
            View = new Lazy<object>(() => ServiceLocator.Current.GetInstance<OrderView>()),
            Position = ViewPosition.Center,
            Title = "New Order"
        });
    }

    private static void ViewOrders()
    {
        //to be implemented
    }
}

Notice the OrderView being created by the ServiceLocator to allow dependency injection, if needed.

The MainContainer control is responsible for subscribing this message and display the view inside a tab control.

public partial class MainContainer : UserControl
{
    public MainContainer()
    {
        InitializeComponent();
        if (DesignerProperties.IsInDesignTool)
            return;

        Messenger.Get<CreateViewMessage>().Subscribe(this.HandleCreateView, ThreadOption.UIThread, true, this.CanHandleCreateView);
    }

    private bool CanHandleCreateView(CreateViewMessageArgs args)
    {
        return args.Position == ViewPosition.Center;
    }

    private void HandleCreateView(CreateViewMessageArgs args)
    {
        this.tabCtrl.Items.Add(new TabItem
        {
            Header = args.Title, Content = args.View.Value,
            VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch,
            HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch
        });
    }
}

When you subscribe a message you can specify a predicate that tells the publish mechanism wether you really want to handle that message. In this case, the main container only wants to handle CreateViewMessages that have views to be displayed in the Center position. I could have another component subscribing the CreateViewMessage and display views inside windows whenever the position is Floating. The ThreadOption parameter is also important. Every message that deals with UI should be subscribed in the UI Thread. If you want to plug in a logger to subscribe/log messages you don’t need to subscribe on the UI thread. In that case you should use a background thread.

This is a very simple aproach on how to do UI composition which works and is very effective. The source code repository has been updated as well.

In the next post I will focus on viewmodels and how we can build the OrderView.

 
Advertisements

2 thoughts on “Architecting Silverlight LOB applications (Part 5) – Modules and UI composition

  1. Just want to say kudos again. This is the BEST coding example I have come across on the web. Almost in respect to everything. Silverlight, best practices, abstraction, prism, mvvm etc. Thanks for all the work.

    Ed

  2. Can’t seem to get the Messenger object to compile from your source? giving me a error on the generic Type T being passed to getEvent

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s