Introduction
To get started with the bootstrapper you need the following three things:
- Extension interface
- Strategy
- Bootstrapper setup
Extension interface
The extension interface defines the extension points which are called by the bootstrapper. The custom extension interface must inherit from IExtension and can only declare methods which return void as extension points. A very basic extension interface could look like the following:
The extension interface above would indicate that the applications extension points are:
- Start
- Stop
This allows inheritors to override only the extension points that they are interested in.
Note: IExtension inherits from IDescribable which defines a name and a description. The name and the description are consumed by the reporting capability. The more expressive your names and descriptions are the more meaningful your report will be!
Strategy
The strategy defines the order of execution for extension points. The custom strategy must inherit from IStrategy. For convenience there is a abstract base class AbstractStrategy{TExtension} which simplifies defining a custom strategy. A very basic strategy could look like the following:
The strategy above would configure the applications startup and shutdown phase to:
- During startup call the extension point Start on all registered extensions
- During shutdown call the extension point Stop on all registered extensions
Bootstrapper setup
The bootstrapper setup is simply and straight forward. You can always follow the following steps:
- Instantiate a new DefaultBoostrapper
- Instantiate a new strategy
- Initialize the bootstrapper with the strategy
- Add the required extensions
- Call Run when the application starts
- Call Shutdown and then Dispose when the application stops.
Note:DefaultBootstrapper{TExtension} and AbstractStrategy{TExtension} implement IDisposable. The bootstrapper takes care of disposing the strategy and the extensions if the correct behavior is attached. If you are using FxCop to check your compiled code it will produces warnings that you should dispose the strategy and the extensions which implement IDisposable before the references move out of scope. You can safely suppress these warnings.
Advanced
The bootstrapper can do more! Let us look into a more complex scenario. Often it is required to collect context information during the bootstrapping process and pass this information to the extension points.
Imagine you are using an inversion of control container which intakes IModule implementations to register dependencies during the bootstrapping process. All IModule implementations need to be passed into the IContainer implementation upon construction. For backward compatibility the bootstrapper must automatically check whether an extension implements IModuleProvider and register the provided modules on the IContainer implementation.
The application we are designing allocates both managed and unmanaged resources which need to be removed when the application is shutting down. To get this behavior some extension occasionally implement IDisposable which needs to be called when shutting down.
Furthermore the application uses configuration sections to be able to overwrite some default configurations without recompiling the application. These configuration entries must be parsed and passed to the extensions which rely on their existence.
Extension interface
A custom extension interface for this example could look like the following:
The extension interface above would indicate that the applications extension points are:
- Start
- ContainerInitializing which allows to add IModule implementations
- ContainerInitialized which allows to resolve dependencies on the IContainer
- Ready
- Stop
This allows inheritors to override only the extension points that they are interested in. The extension interface does not include:
- the fact that the extensions must be checked whether they implement IModuleProvider
- the fact that the extensions must be checked whether they implement IDisposable
- the fact that configuration sections must be loaded for an extension if available
Behaviors
Behaviors allow extending the bootstrapping process in an aspect oriented style. Behaviors gain access to extensions which are participating in the bootstrapper process and can therefore influence them for example by injecting additional runtime information into an extension. Behaviors must implement IBehavior
Let's us look into an example. Imagine you have the following module provider interface below:
The behavior below demonstrates how extensions can be scanned whether they are module providers. If an extension implements IModuleProvider all provided modules are automatically collected in the module collection the behavior receives upon construction.
Below is a sample IModule implementation which uses a made up fluent syntax to register dependencies.
Below an extension which implements IModuleProvider and yield returns a custom module. Note:Of course the custom module could also have been added by overriding ContainerInitializing and adding the module to the collection. But the sample should demonstrate the power of behaviors.
The DependencyUsingBehavior is a special behavior which does not consume extensions but execute an operation on a dependency. The dependency is resolved by using constructor injection. To enable constructor injection when constructing the DependencyUsingBehavior the IContainer implementation must already be built up and the required dependencies registered.
The DisposeExtensionBehavior calls Dispose on all extensions which implement IDisposable.
Now all these pieces must be sticked together in the strategy. Let's look how to define it.
Strategy
The strategy defines the order of execution for extension points and behaviors. The custom strategy must inherit from IStrategy. For convenience, use the provided abstract base class AbstractStrategy{TExtension} which simplifies defining a custom strategy. The strategy could look like the following:
The strategy above would configure the applications startup and shutdown phase to:
- During startup
- before calling any extension point load configuration sections
- and then load extension configuration sections.
- Call the extension point Start on all registered extensions
- Initialize the context with this.modules and call the extension point ContainerInitializing with the context on all registered extensions
- Execute the behavior ModuleProvidingBehavior which gains access to the context.
- Initialize the context by calling BuildContainer and call the extension point ContainerInitialized with the context on all registered extensions
- Execute the lazy resolved IBehavior
- Call the extension point Ready on all registered extensions
- During shutdown
- call the extension point Stop on all registered extensions
- dispose all extensions which implement IDisposable