Sunday, January 30, 2011

Composite Web Apps With Prism

Why Prism

Your first experience with Silverlight was probably something small: a video player, a simple charting application, or even a menu. These types of applications are simple and straightforward to design, and segmenting them into rigorous layers with separate responsibilities is overkill. Problems surface, however, when you try to apply a tightly coupled style to large applications. As the number of moving parts grows, the simple style of application development falls apart. Part of the remedy is layering, but a tightly coupled architecture is just one of a number of problems that need to be solved in large Silverlight projects. In this article, I show you how to build an application using the composition techniques of the Composite Application Library from the Prism project. The example I develop is a simple editor of database data. As requirements change and a project matures, it is helpful if you can change parts of the application without having these changes cascade throughout the system. Modularizing an application allows you to build application components separately (and loosely coupled) and to change whole parts of your application without affecting the rest of the code. Also, you might not want to load all the pieces of your application at once. Imagine a customer management application in which users log on and can then manage their prospect pipeline as well as check e-mail from any of their prospects. If a user checks e-mail several times a day but manages the pipeline only every day or two, why load the code to manage the pipeline until it is needed? It would be great if an application supported on-demand loading of parts of the application, a situation that can be addressed by modularizing the application. The patterns & practices team at Microsoft created a project called Prism (or CompositeWPF) that is meant to address problems like these for Windows Presentation Foundation (WPF) applications, and Prism has been updated to support Silverlight as well. The Prism package is a mix of framework and guidance for building applications. The framework, called the Component Application Library (CAL), enables the following:
  • Application modularity: Build applications from partitioned components.
  • UI composition: Allows loosely coupled components to form user interfaces without discrete knowledge of the rest of the application.
  • Service location: Separate horizontal services (for example, logging and authentication) from vertical services (business logic) to promote clean layering of an application.
The CAL is written with these same design principles in mind, and for application developers it is a buffet-style framework—take what you need and leave the rest. Figure 1 shows the basic layout of the CAL in relation to your own application.
Figure 1 Composite Application Library
The CAL supports these services to aid you in composing your application from smaller parts. This means the CAL handles which pieces are loaded (and when) as well as providing base functionality. You can decide which of these capabilities help you do your job and which might get in the way. My example in this article uses as much of the CAL as possible. It is a shell application that uses the CAL to load several modules at run time, place views in regions (as shown in Figure 2), and support services. But before we get to that code, you need to understand some basic concepts about dependency injection (also called Inversion of Control, or IoC). Many of the CAL's features rely on dependency injection, so understanding the basics will help you develop the architecture of your Silverlight project with Prism.
Figure 2 Composite Application Architecture

Introducing Dependency Injection

In typical development, a project starts with an entry point (an executable, a default.aspx page, and so on). You might develop your application as one giant project, but in most cases some level of modularity exists in that your application loads a number of assemblies that are part of the project. The main assembly knows what assemblies it needs and creates hard references to those pieces. At compile time, the main project knows about all the referenced assemblies, and the user interface consists of static controls. The application is in control of what code it needs and usually knows all the code it might use. This becomes a problem, however, because development takes place inside the main application project. As a monolithic application grows, build time and conflicting changes can slow down development.

Dependency injection aims to reverse this situation by providing instructions that set up dependencies at run time. Instead of the project controlling these dependencies, a piece of code called a container is responsible for injecting them.

But why is this important? For one thing, modularizing your code should make it easier to test. Being able to swap out a project's dependencies enables cleaner testing so that only the code to be tested can be the source of a test failing, instead of code somewhere in the nested chain of dependencies. Here's a concrete example. Imagine you have a component that other developers use to look up addresses for particular companies. Your component depends on a data access component that retrieves the data for you. When you test your component, you start by testing it against the database, and some of the tests fail. But because the schema and builds of the database are constantly changing, you don't know whether your tests are failing because of your own code or the data access code. With your component's hard dependency on the data access component, testing the application becomes unreliable and causes churn while you track down failures in your code or in other's code.

Your component might look something like this:

public class AddressComponent
{
DataAccessComponent data = new DataAccessComponent();

public AddressComponent()
{
}

...
}
Instead of a hard-wired component, you could accept an interface that represents your data access, as shown here:

public interface IDataAccess
{
...
}

public class AddressComponent
{
IDataAccess data;

public AddressComponent(IDataAccess da)
{
data = da;
}

...
}


Ordinarily, an interface is used so you can create a version that allows you to adjust your code. This approach is often called "mocking." Mocking means creating an implementation of the dependency that does not actually represent the real version. Literally, you're creating a mock implementation.
This approach is better because the dependency (IDataAccess) can be injected into the project during construction of the object. The implementation of the IDataAccess component will depend on the requirements (testing or real).

That's essentially how dependency injection works, but how is the injection handled? The job of the container is to handle creation of the types, which it does by allowing you to register types and then resolving them. For example, assume you have a concrete class that implements the IDataAccess interface. During start up of the application, you can tell the container to register the type. Anywhere else in your application where you need the type, you can ask the container to resolve the type, as shown here:

public void App_Startup()
{
container.RegisterType();
}

...

public void GetData()
{
IDataAccess acc = container.Resolve();
}
Depending on the situation (testing or production), you can swap out the implementation of IDataAccess simply by changing the registration. Additionally, the container can handle construction injection of dependencies. If an object that needs to be created by the container's constructor takes an interface that the container can resolve, it resolves the type and passes it to the constructor, as shown in Figure 3.

public class AddressComponent : IAddressComponent
{
IDataAccess data;

public AddressComponent(IDataAccess da)
{
data = da;
}
}

...

public void App_Startup()
{
container.RegisterType();
container.RegisterType();
}

public void GetAddresses()
{
// When we ask the container to create the AddressComponent,
// it sees that a constructor takes a IDataAccess object
// so it automatically resolves that dependency
IAddressComponent addr = container.Resolve();
}


Notice that the AddressComponent's constructor takes an implementation of IDataAccess. When the constructor creates the AddressComponent class during resolution, it automatically creates the instance of IDataAccess and passes it to the AddressComponent.

When you register types with the container, you also tell the container to deal with the lifetime of the type in special ways. For example, if you are working with a logging component, you might want to treat it as a singleton so that every part of the application that needs logging does not get its own copy (which is the default behavior). To do this, you can supply an implementation of the abstract LifetimeManager class. Several lifetime managers are supported. ContainerControlledLifetimeManager is a singleton per process and PerThreadLifetimeManager is a singleton per thread. For ExternallyControlledLifetimeManager, the container holds a weak reference to the singleton. If the object is released externally, the container creates a new instance, otherwise it returns the live object contained in the weak reference.

You use the LifetimeManager class by specifying it when registering a type. Here's an example:

container.RegisterType(  new ContainerControlledLifetimeManager());


In the CAL, the IoC container is based on the Unity framework from the patterns & practices group. I'll use the Unity container in the following examples, but there are also a number of open source alternatives to the Unity IoC container, such as Ninject, Spring.NET, Castle, and StructureMap. If you are familiar with and already using an IoC container other than Unity, you can supply your own container (although it takes a little more effort).

Startup Behavior

Ordinarily in a Silverlight application, the startup behavior is simply to create the main XAML page's class and assign it to the application's RootVisual property. In a composite application, this work is still required, but instead of creating the XAML page class, a composite application typically uses a bootstrapping class to handle startup behavior.

To start, you need a new class that derives from the UnityBootstrapper class. This class is in the Microsoft.Practices.Composite.UnityExtensions assembly. The bootstrapper contains overridable methods that handle different parts of startup behavior. Often, you will not override every startup method, only the ones necessary. The two methods you must override are CreateShell and GetModuleCatalog.

The CreateShell method is where the main XAML class is created. This is typically called the shell because it is the visual container for the application's components. My example includes a bootstrapper that creates a new instance of the Shell class and assigns it to RootVisual before returning this new Shell class, as shown here:

public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
Shell theShell = new Shell();
App.Current.RootVisual = theShell;
return theShell;
}

protected override IModuleCatalog GetModuleCatalog()
{
...
}
}


The GetModuleCatalog method, which I'll explain in the next section, returns the list of modules to load.

Now that you have a bootstrapper class, you can use it in your Silverlight application's startup method. Usually, you create a new instance of the bootstrapper class and call its Run method, as shown in Figure 4.

public partial class App : Application
{

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

InitializeComponent();
}

private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper boot = new Bootstrapper();
boot.Run();
}

...
}


The bootstrapper is also involved in registering types with the container that different parts of the application require. To accomplish this, you override the ConfigureContainer method of the bootstrapper. This gives you a chance to register any types that are going to be used by the rest of the application. Figure 5 shows the code.

public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
Container.RegisterType();
base.ConfigureContainer();
}

protected override DependencyObject CreateShell()
{
// Get the provider for the shell
IShellProvider shellProvider = Container.Resolve();

// Tell the provider to create the shell
UIElement theShell = shellProvider.CreateShell();

// Assign the shell to the root visual of our App
App.Current.RootVisual = theShell;

// Return the Shell
return theShell;
}

protected override IModuleCatalog GetModuleCatalog()
{
...
}
}


Here, the code registers an interface for a class that implements the IShellProvider interface, which is created in our example and is not part of the CAL framework. That way we can use it in our implementation of the CreateShell method. We can resolve the interface and then use it to create an instance of the shell so we can assign it to RootVisual and return it. This methodology may seem like extra work, but as you delve into how the CAL helps you build your application, it becomes clear how this bootstrapper is helping you. Edit

Modularity

In a typical .NET environment, the assembly is the main unit of work. This designation allows developers to work on their code separately from each other. In the CAL, each of these units of work is a module, and for the CAL to use a module, it needs a class that can communicate the module's startup behavior. This class also needs to supports the IModule interface. The IModule interface requires a single method called Initialize that allows the module to set itself up to be used in the rest of the application. The example includes a ServerLogger module that contains the logging capabilities for our application. The ServerLoggingModule class supports the IModule interface as shown here:

public class ServerLoggerModule : IModule
{
public void Initialize()
{
...
}
}


The problem is that we don't know what we want to initialize in our module. Since it's a ServerLogging module, it seems logical that we want to register a type that does logging for us. We want to use the container to register the type so that whoever needs the logging facility can simply use our implementation without knowing the exact type of logging it performs.

We get the container by creating a constructor that takes the IUnityContainer interface. If you remember the discussion of dependency injection, the container uses constructor injection to add types that it knows about. IUnityContainer represents the container in our application, so if we add that constructor, we can then save it and use it in our initialization like so:

public class ServerLoggerModule : IModule
{
IUnityContainer theContainer;

public ServerLoggerModule(IUnityContainer container)
{
theContainer = container;
}

public void Initialize()
{
theContainer.RegisterType(
new ContainerControlledLifetimeManager());
}
}


Once initialized, this module is responsible for the logging implementation for the application. But how does this module get loaded?

When using the CAL to compose an application, you need to create a ModuleCatalog that contains all the modules for the application. You create this catalog by overriding the bootstrapper's GetModuleCatalog call. In Silverlight, you can populate this catalog with code or with XAML.

With code, you create a new instance of the ModuleCatalog class and populate it with the modules. For example, look at this:

protected override IModuleCatalog GetModuleCatalog()
{
var logModule = new ModuleInfo()
{
ModuleName = "ServerLogger",
ModuleType = "ServerLogger.ServerLoggerModule, ServerLogger, Version = 1.0.0.0"
};

var catalog = new ModuleCatalog();
catalog.AddModule(logModule);

return catalog;
}


Here, I simply add a single module called ServerLogger, the type defined in the ModuleInfo's ModuleType property. In addition, you can specify dependencies between modules. Because some modules might depend on others, using dependencies helps the catalog know the order in which to bring in the dependencies. Using the ModuleInfo.DependsOn property, you can specify which named modules are required to load another module.

You can load the catalog directly from a XAML file, as shown here:

protected override IModuleCatalog GetModuleCatalog()
{
var catalog = ModuleCatalog.CreateFromXaml(new Uri("catalog.xaml",
UriKind.Relative));
return catalog;

}


The XAML file contains the same type information you can create with code. The benefit of using XAML is that you can change it on the fly. (Imagine retrieving the XAML file from a server or from another location based on which user logged on.) An example of a catalog.xaml file is shown in Figure 6.

In this XAML catalog, the group includes two modules and the second module depends on the first. You could use a specific XAML catalog based on roles or permissions, as you could with code. Once the catalog is loaded by the bootstrapper, it attempts to create instances of the module classes and allow them to initialize themselves. In the code examples here, the types have to be referenced by the application (therefore, already loaded into memory) for this catalog to work. This is where this facility becomes indispensible to Silverlight. Although the unit of work is the assembly, you can specify a .xap file that contains the module or modules. To do this, you specify a Ref value in ModuleInfo. The Ref value is a path to the .xap file that contains the module: protected override IModuleCatalog GetModuleCatalog() { var logModule = new ModuleInfo() { ModuleName = "ServerLogger", ModuleType = "ServerLogger.ServerLoggerModule, ServerLogger, Version= 1.0.0.0", Ref = "ServerLogger.xap" }; var catalog = new ModuleCatalog(); catalog.AddModule(logModule); return catalog; } When you specify a .xap file, the bootstrapper knows that the assembly is not available and goes out to the server and retrieves the .xap file asynchronously. Once the .xap file is loaded, Prism loads the assembly and creates the module type and initializes the module. For .xap files that contain multiple modules, you can create a ModuleGroup that contains a set of ModuleInfo objects and set the Ref of the ModuleGroup to load all those modules from a single .xap file: var modGroup = new ModuleInfoGroup(); modGroup.Ref = "MyMods.xap"; modGroup.Add(logModule); modGroup.Add(dataModule); modGroup.Add(viewModule); var catalog = new ModuleCatalog(); catalog.AddGroup(modGroup); For Silverlight applications, this is a way to compose your applications from multiple .xap files, which allows you to version different sections of your composed application separately. When creating Silverlight modules to be housed in a .xap file, you create a Silverlight Application (not a Silverlight Library). Then you reference all the module projects you want to put in the .xap file. You need to remove the app.xaml and page.xaml files because this .xap file will not be loaded and run like a typical .xap file. The .xap file is just a container (could be a .zip file, it doesn't matter). Also, if you are referencing projects that are already referenced in the main project, you can change those references to Copy Local=false in the properties because you don't need the assemblies in the .xap file (the main application has already loaded them, so the catalog will not try to load them a second time.) But loading a huge application with multiple calls across the wire does not seem like it would help performance. That is where the ModuleInfo's InitializationMode property comes into play. InitializationMode supports two modes: WhenAvailable, in which the .xap file is loaded asynchronously and then initialized (this is the default behavior), and OnDemand, in which the .xap is loaded when explicitly requested. Since the module catalog does not know the types in the modules until initialization, resolving types that are initialized with OnDemand will fail. On-demand support for modules and groups allows you to load certain functionality in a large application as needed. Startup time is accelerated, and other required code can be loaded as users interact with an application. This is a great feature to use when you have authorization to separate parts of an application. Users who need only a few parts of the application do not have to download code they'll never use. To load a module on demand, you need access to an IModuleManager interface. Most often, you request this in the constructor of the class that needs to load a module on demand. Then you use IModuleManager to load the module by calling LoadModule, as shown in Figure 7. public class GameListViewModel : IGameListViewModel { IModuleManager theModuleManager = null; public GameListViewModel(IModuleManager modMgr) { theModuleManager = modMgr; } void theModel_LoadGamesComplete(object sender, LoadEntityCompleteEventArgs e) { ... // Since we now have games, let's load the detail pane theModuleManager.LoadModule("GameEditor.GameDetails"); } } Modules are simply the unit of modularization in your applications. In Silverlight, treat a module much like you would a library project, but with the extra work of module initialization, you can decouple your modules from the main project.

UI Composition

In a typical Explorer application, the left pane displays a list or tree of information and the right side contains details about the item selected in the left pane. In the CAL, these areas are called regions. The CAL supports defining regions directly in XAML by using an attached property on the RegionManager class. This property allows you to specify regions in your shell and then indicate what views should be hosted in the region. For example, our shell has two regions, LookupRegion and DetailRegion, as shown here:


A RegionName can be applied to the an ItemsControl and its derived controls (for example, ListBox); Selector and its derived controls (for example, TabControl); and ContentControl and its derived controls (for example, ScrollViewer).

Once you define regions, you can direct modules to load their views into the regions by using the IRegionManager interface, as shown here:

public class GameListModule : IModule
{
IRegionManager regionManager = null;

public GameListModule(IRegionManager mgr)
{
regionManager = mgr;
}

public void Initialize()
{
// Build the View
var view = new GameListView();

// Show it in the region
regionManager.AddToRegion("LookupRegion", view);
}
}


This facility allows you to define regions in your application where views can appear and then have the modules define how to place views into the region, allowing the shell to be completely ignorant of the views.

The behavior of the regions might be different depending on the control type being hosted. The example uses a ScrollViewer so that one and only one view can be added to the region. In contrast, ItemControl regions allow for multiple views. As each view is added, it shows up as a new item in the ItemsControl. That facility makes it easier to build functionality like a dashboard.

If you are using an MVVM pattern to define your views, you can mix the regions and the service location aspects of the container to make your views and view models ignorant of each other and then let the module join them at run time. For example, if I change the GameListModule, I can register views and views models with the container and then join them before applying the view to the region, as shown in Figure 8.

public class GameListModule : IModule
{
IRegionManager regionManager = null;
IUnityContainer container = null;

public GameListModule(IUnityContainer con, IRegionManager mgr)
{
regionManager = mgr;
container = con;
}

public void Initialize()
{
RegisterServices();

// Build the View
var view = container.Resolve();

// Get an Implemenation of IViewModel
var viewModel = container.Resolve();

// Marry Them
view.ApplyModel(viewModel);

// Show it in the region
regionManager.AddToRegion("LookupRegion", view);
}

void RegisterServices()
{
container.RegisterType();
container.RegisterType();
}

Event Aggregation

After you have multiple views in your applications through UI composition, you face a common problem. Even though you have built independent views to support better testing and development, there are often touch points where the views cannot be completely isolated. They are logically coupled because they need to communicate, but you want to keep them as loosely coupled as possible regardless of the logical coupling.

To enable loose coupling and cross-view communication, the CAL supports a service called event aggregation. Event aggregation allows access to different parts of the code to publishers and consumers of global events. Such access provides a straightforward way of communicating without being tightly coupled and is accomplished using the CAL's IEventAggregator interface. IEventAggregator allows you to publish and subscribe to events across the different modules of your application.

Before you can communicate, you need a class that derives from EventBase. Typically, you create a simple event that derives from the CompositePresentationEvent class. This generic class allows you to specify the payload of the event you are going to publish. In this case, the GameListViewModel is going to publish an event after a game is selected so that other controls that want to change their context as the user selects a game can subscribe to that event. Our event class looks like the following:

public class GameSelectedEvent : CompositePresentationEvent
{

}


Once the event is defined, the event aggregator can publish the event by calling its GetEvent method. This retrieves the singleton event that is going to be aggregated. The first one who calls this method creates the singleton. From the event, you can call the Publish method to create the event. Publishing the event is like firing an event. You do not need to publish the event until it needs to send information. For example, when a game is selected in the GameList, our example publishes the selected game using the new event:

// Fire Selection Changed with Global Event
theEventAggregator.GetEvent().Publish(o as Game);


In other parts of your composed application, you can subscribe to the event to be called after the event is published. The Subscribe method of the event allows you to specify the method to be called when the event is published, an option that allows you to request threading semantics for calling the event (for example, the UI thread is commonly used), and whether to have the aggregator hold a reference to the passed in information so that it is not subject to garbage collection:

// Register for the aggregated event
aggregator.GetEvent().Subscribe(SetGame,
ThreadOption.UIThread,
false);


As a subscriber, you can also specify filters that are called only in specific situations. Imagine an event that returns the state of an application and a filter that is called only during certain data states.




}


This approach allows you to use UI composition while maintaining the strict separation of MVVM.

Delegate Commands

In Silverlight (unlike WPF), a true commanding infrastructure does not exist. This forces the use of code-behind in views to accomplish tasks that would be accomplished more readily directly in XAML using the commanding infrastructure. Until Silverlight supports this facility, the CAL supports a class that helps solve this problem: the DelegateCommand.

To get started with DelegateCommand, you need to define the DelegateCommand in your ViewModel so you can data-bind to it. In the ViewModel, you would create a new DelegateCommand. The DelegateCommand expects the type of data to be sent to it (often Object if no data is used) and one or two callback methods (or lambda functions). The first of these methods is the action to execute when the command is fired. Optionally, you can specify a second callback to be called to test whether the command can be fired. The idea is to enable the disabling of objects in the UI (buttons, for example) when it is not valid to fire the command. For example, our GameDetailsViewModel contains a command to support saving data:

// Create the DelegateCommand
SaveCommand = new DelegateCommand(c => Save(), c => CanSave());


When SaveCommand is executed, it calls the Save method on our ViewModel. The CanSave method is then called to make sure the command is valid. This allows the DelegateCommand to disable the UI if necessary. As the state of the view changes, you can call the DelegateCommand.RaiseCanExecuteChanged method to force a new inspection of the CanSave method to enable or disable the UI as necessary.

To bind this to XAML, use the Click.Command attached property that is in the Microsoft.Practices.Composite.Presentation.Commands namespace. Then bind the value of the command to be the command you have in your ViewModel, like so:



Now when the Click event is fired, our command is executed. If you want, you can specify a command parameter to be sent to the command so you can reuse it.
As you might be wondering, the only command that exists in the CAL is the Click event for a button (or any other selector). But the classes you can use to write your own commands are fairly straightforward. The sample code includes a command for SelectionChanged on a ListBox/ComboBox. This command is called the SelectorCommandBehavior and derives from the CommandBehaviorBase class. Looking at the custom command behavior implementation will provide you with a starting place to write your own command behaviors.

No comments: