Saturday, January 8, 2011

Jounce Part 9: Static and Dynamic Module Management

One popular feature in the widely used Prism Framework is the concept of "modules." A prism module is a functional unit that represents a set of related concerns. These concerns can be coded and tested independently and then composed into larger applications. They can also handle dependencies with other modules and be discovered and dynamically loaded.

While the concept worked well for WPF and prior versions of Silverlight, Jounce takes a slightly different approach that recognizes and uses two features that are built into Silverlight. The first is the concept of a XAP file, which is nothing more than a ZIP file with a manifest that lists resources and contains the resources themselves (typically DLLs, images, embedded fonts and data). Jounce treats XAP files as the perfect medium to encapsulate a module within Silverlight. The second feature is the MEF DeploymentCatalog which allows Silverlight to dynamically load and compose parts from a XAP file.

Creating a Module

To create a module with Jounce is incredibly simple: fire up Visual Studio and create a new Silverlight Application. That is a module!

There will always be exactly one module that is a "host" module. This module must contain the App.xaml.cs code and the initial reference to the Jounce ApplicationService in order to start the application and initialize the Jounce framework. Additional modules may be standalone. For those modules, you can simply create a new application and then delete the App.xaml and MainPage.xaml files that are auto-generated by the template. Instead, you can begin defining views, view models, or other class that you simply export. When the module is loaded by Jounce, these will be automatically imported and composed into the main application.

An example of this is in the Jounce "DynamicXapCalculator" quickstart. The main module is a typical Silverlight application. It defines a display that contains a row to enter digits, a row of "commands" and an "equals" row. It is designed to function like a calculator, but the buttons are not pre-defined. This is the XAML for the button row:

<ItemsControl Grid.Row="2" HorizontalAlignment="Center" ItemsSource="{Binding Commands}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Width="50" Height="50" Margin="5" Content="{Binding Item1}" Command="{Binding Item2}"/>
        </DataTemplate>                    
    </ItemsControl.ItemTemplate>
</ItemsControl>

A quick peek at the view model demonstrates how the Managed Extensibility Framework (MEF) is used to compose the commands. The definition is a list of tuples that represent the display and an ICommand implementation:

[ImportMany(AllowRecomposition = true)]
public ObservableCollection<Tuple<string,ICommand>> Commands { get; set; }

The view model itself exports two implementations of this construct: one for addition, and one for subtraction.

[Export]
public Tuple<string,ICommand> AddCommand
{
    get
    {
        return Tuple.Create("+",
                            (ICommand)new ActionCommand<object>(
                                obj => ExecuteCommand((oldValue, newValue) => oldValue + newValue)));
    }
}

[Export]
public Tuple<string, ICommand> SubstractCommand
{
    get
    {
        return Tuple.Create("-",
                            (ICommand)new ActionCommand<object>(
                                obj => ExecuteCommand((oldValue, newValue) => oldValue - newValue)));
    }
}

The view model itself keeps tracks of values, so the commands simply access those existing values and manipulate those. When you run the program, you are presented with a calculator that you can use to add and subtract.

The idea is that the calculator can be expanded by modules. Other modules might address scientific notation, exponents, factorials, trigonometry functions, or other areas that can be independently built and tested, then merged into the application. For our simple example, the external module supplies code for division and multiplication.

As I wrote earlier, creating the new module (the poorly spelled DyanmicXapCalculatorAdvanced) started with a new Silverlight application. The App.xaml and MainPage.xaml files were deleted and a new class added to export the new commands.

public class AdvancedFunctions
{        
    [Import]
    public Action<Func<int, int, int>> ExecuteCommandImport { get; set; }

    [Export]
    public Tuple<string, ICommand> AddCommand
    {
        get
        {
            return Tuple.Create("*",
                                (ICommand)new ActionCommand<object>(
                                    obj => 
                                        ExecuteCommandImport((oldValue, newValue) => oldValue * newValue)));
        }
    }

    [Export]
    public Tuple<string, ICommand> SubstractCommand
    {
        get
        {
            return Tuple.Create("/",
                                (ICommand)new ActionCommand<object>(
                                    obj =>
                                            ExecuteCommandImport((oldValue, newValue) => newValue == 0 ? 0 : oldValue / newValue)));
        }
    }
}

Notice that the actual "execution" structure that references the values the view model is holding can be imported as a generic action in this module that the exported commands call. This is "bound" by the export in the original module, which exports the method that handles the command executions:

[Export]
public Action<Func<int,int,int>> ExecuteCommandExport
{
    get { return ExecuteCommand; }
}

The syntax above works because ExecuteCommand has the signature specified by the action being exported.

Dynamically Loading Modules

The calculator must have some way to bring in the dynamic module. In this case, it will do so directly. It does this by exposing a load button wired to a load command that indicates the user is ready to bring in the additional functionality. There are two simple steps to load a dynamic module in Jounce. First, reference the IDeploymentService:

[Import]
public IDeploymentService Deployment { get; set; }

Second, call the RequestXap method and pass the URI of the XAP resource. In the example, it is contained in the same ClientBin directory as the host application, so we can use a relative reference. The method has two overrides. One is a "blind" call or "fire and forget." The second will call a delegate once the module is loaded and pass in an Exception object in case something went wrong. For this example, any errors are ignored. If it succeeds, a loaded flag is set and the command for loading notified, so it can be disabled to prevent the user from trying to load it again (this would just be redundant: Jounce keeps track of dynamic modules and won't try to load the same module more than once).

private void _Load()
{
    Deployment.RequestXap("DyanmicXapCalculatorAdvanced.xap",ex=>
                                                                    {
                                                                        if (ex == null) return;
                                                                        _loaded = true;
                                                                        ((IActionCommand)LoadCommand).RaiseCanExecuteChanged();
                                                                    });

}

That's all there is to it. Run the application and click the load button. You will see that the new operators instantly appear and allow you to perform division and multiplication as well as addition and subtraction.

Routed Modules

Of course, explicitly loading modules isn't required. It may be that the module load is triggered as a part of navigation. In that case, you can simply route the views that a module contains to the XAP file for the module, and Jounce will automatically load the dynamic module when it is required. An example of this is in the RegionManagement quick start. This quickstart illustrates how to use Jounce to wire views into regions. In the code-behind for the Square view, you'll see an exported route that maps the view tagged "Dynamic" with the module "RegionManagementDynamic.xap":

[Export]
public ViewXapRoute DynamicRoute
{
    get
    {
        return ViewXapRoute.Create("Dynamic", "RegionManagementDynamic.xap");
    }
}

The view that is tagged "Dynamic" actually exists in a completely separate XAP file (RegionManagementDynamic.xap). You can also create routes on the fly at runtime. This example will do the same thing:

[Import]
public ViewRouter ViewRouter { get; set; }

public ConfigureRoute()
{
    ViewRouter.RouteViewInXap("Dynamic", "RegionManagementDynamic.xap");
}

When the navigation event for the view is fired, Jounce will automatically determine whether the module has been loaded and, if not, will fetch it dynamically. This happens in the click event for the dynamic button:

private void ButtonDynamic_Click(object sender, System.Windows.RoutedEventArgs e)
{
    EventAggregator.Publish(new ViewNavigationArgs("Dynamic"));
}

Module Initialization

When Jounce loads a dynamic module, it passes it to a special MEF catalog known as a DeploymentCatalog. MEF processes the parts in the module and recomposes any imports that can take advantage of the new exports. In the calculator example, MEF will take the command export from the host application and inject it in the advanced functions type, then find the command exports and bring those back into the calculator view model.

When you run the region management example, you'll notice after clicking the dynamic button, a message pops up indicating the module has loaded. This is done in the dynamic module using the module initializer. Any module in Jounce can provide code to be called when new modules are loaded. To take advantage of this, simply implement IModuleInitializer in one or more of your classes and export it. The module initializer may be used in the main module as well, and will be called after all of the composition is completed after the application first starts.

The interface declares a property to track if the module is initialized, and a method to call for initialization. Typically, you would implement the method and set the initialized flag to "true." If you want your module to be notified whenever any new module is loaded, however, you can leave the flag unset and it will be called each time until you set the flag. This is useful if your module must inspect certain properties or fields and respond when plug-ins or other extensions are loaded.

The implementation in the region management example is simple:

[Export(typeof(IModuleInitializer))]
public class ModuleInit : IModuleInitializer 
{
    public bool Initialized { get; set; }
        
    public void Initialize()
    {
        JounceHelper.ExecuteOnUI(()=>MessageBox.Show("Dynamic module loaded."));
        Initialized = true;
    }
}

As you can see, it simply displays a message and then sets the flag.

Summary

The Prism concept of modules is extremely powerful and provides multiple means to configure and discover modules, as well as multiple methods to load them. You can read the Prism documentation about modules here. Jounce takes a more simple and native Silverlight approach by encapsulating modules in XAP files and using the deployment service to load the modules. This is intended to make it quick and easy, but provide powerful flexibility. For example, you could easily expose a web service that provides names for plug-ins and then use the deployment service to dynamically load them, or scan a directory for plug-ins on the server, inject the names in the initParams collection and create routes at runtime to allow the user to select and load modules. Providing any "bootstrap" code you need for your module is as easy as implementing the IModuleInitializer interface and exporting it.

Jeremy Likness

3 comments:

  1. Hi Jeremy, first off, thanks for your contribution to the community. The Jounce framework is really slick and addresses many of the needs that I have from a UI standpoint. You have potentially saved me much time, so thanks for that.

    Admittedely, I've only been looking into Jounce for a few hours, so forgive me if my questions are premature.

    Sometimes having the view invoke members on the VM are a complete pain, and it's often (depending on which controls you use) easier just to handle an event in the code behind and call the VM directly. Does Jounce support this? I can't see how it does this due to the dynamic binding that occurs between the 2 at runtime.

    I also have a need to create a portal that hosts different applications. So my question is, seeing as though only one view can be the "shell", can my sub programs also utilize the concept of the shell? Or maybe I'm going about this the wrong way? But basically, the main shell (or portal) will display links/buttons that will dynamically load the application into a main content area when clicked.

    Lastly, within one of these programs (or maybe even across open programs), when the content of one control changes, I need any other controls currently loaded to be able to subscribe for change notifications. Again, I haven't had much time to really dig into Jounce, but it seems that the VisualStateAggregator might help me achieve this behavior?

    Any direction in terms of past posts or samples would be greatly appreciated!

    -Aaron

    ReplyDelete
  2. Sure, Aaron, thanks for asking. Can you do me a huge favor though? I'd like to answer, but I'm sure others are having similar problems and will benefit as well. Can you cut & paste your questions above to the discussion section at CodePlex? I'll be glad to answer there and then other people who have the same question will get an easy and fast answer as well (good news is the short answer is "yes" to all of the above).

    Here's the link to discussions:
    http://jounce.codeplex.com/discussions

    ReplyDelete
  3. Hi Jeremy, how to apply different control templates for the dynamically loaded buttons?

    ReplyDelete