This is Part 3 of a series of articles about a Clifton Method Core Component in which you will learn about Bootstrapping with the Module Manager and the Service Manager.
Series of Articles
Introduction
In the previous two articles, I described the Module Manager, for dynamically loading modules, and the Service Manager, for implementing object instantiation using interface types. In this article, we'll take a breather and look at how to put together a bootstrapper that can be used across pretty much any application, whether it's a WinForm client application, a web server, or other.
Module Initialization
The bootstrapper uses the core class ServiceModuleManager
. This class derives from ModuleManager
and coordinates the initialization of module. As mentioned in the article on the Module Manager, IModule
actually requires the implementation of InitializeServices
:
public interface IModule
{
void InitializeServices(IServiceManager serviceManager);
}
We can see this being applied in the ServiceModuleManager
, which overrides InitializeRegistrants
:
using System;
using System.Collections.Generic;
using Clifton.Core.ServiceManagement;
namespace Clifton.Core.ModuleManagement
{
public class ServiceModuleManager : ModuleManager, IServiceModuleManager
{
public IServiceManager ServiceManager { get; set; }
public virtual void Initialize(IServiceManager svcMgr)
{
ServiceManager = svcMgr;
}
public virtual void FinishedInitialization()
{
}
protected override void InitializeRegistrants(List<IModule> registrants)
{
registrants.ForEach(r =>
{
try
{
r.InitializeServices(ServiceManager);
}
catch (System.Exception ex)
{
throw new ApplicationException("Error initializing " +
r.GetType().AssemblyQualifiedName + "\r\n:" + ex.Message);
}
});
}
}
}

Class and Interface Hierarchy
This gives each module with a class that implements IModule
the opportunity to initialize the services that it provides.
The Bootstrapper
The bootstrapper instantiates, not a ModuleManager
, but a ServiceModuleManager
and performs the two step initialization:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Clifton.Core.Assertions;
using Clifton.Core.ExtensionMethods;
using Clifton.Core.Semantics;
using Clifton.Core.ModuleManagement;
using Clifton.Core.ServiceManagement;
namespace BootstrapDemo
{
static partial class Program
{
public static ServiceManager serviceManager;
public static void InitializeBootstrap()
{
serviceManager = new ServiceManager();
serviceManager.RegisterSingleton<IServiceModuleManager, ServiceModuleManager>();
}
public static void Bootstrap(Action<Exception> onBootstrapException)
{
try
{
IModuleManager moduleMgr =
(IModuleManager)serviceManager.Get<IServiceModuleManager>();
List<AssemblyFileName> modules = GetModuleList(XmlFileName.Create("modules.xml"));
moduleMgr.RegisterModules(modules);
serviceManager.FinishedInitialization();
}
catch (Exception ex)
{
onBootstrapException(ex);
}
}
private static List<AssemblyFileName> GetModuleList(XmlFileName filename)
{
Assert.That(File.Exists(filename.Value), "Module definition file " +
filename.Value + " does not exist.");
XDocument xdoc = XDocument.Load(filename.Value);
return GetModuleList(xdoc);
}
private static List<AssemblyFileName> GetModuleList(XDocument xdoc)
{
List<AssemblyFileName> assemblies = new List<AssemblyFileName>();
(from module in xdoc.Element("Modules").Elements("Module")
select module.Attribute("AssemblyName").Value).ForEach
(s => assemblies.Add(AssemblyFileName.Create(s)));
return assemblies;
}
}
}
I typically implement the bootstrapper as a partial Program
class.

Initialization Workflow
Let's dissect this code a bit. In the bootstrap initialization...
serviceManager = new ServiceManager();
serviceManager.RegisterSingleton<IServiceModuleManager, ServiceModuleManager>();
...we instantiate a ServiceManager
and register the implementor of the ServiceModuleManager
.
Because the ServiceModuleManager
is itself a service, you can replace it with your own initialization process.
Then, in the bootstrapper itself...
IModuleManager moduleMgr = (IModuleManager)serviceManager.Get<IServiceModuleManager>();
List<AssemblyFileName> modules = GetModuleList(XmlFileName.Create("modules.xml"));
moduleMgr.RegisterModules(modules);
serviceManager.FinishedInitialization();
- We acquire the service that initializes the registrants (the modules), which gives each class implementing
IModule
access to the Service Manager.
The singleton returned is cast to an IModuleManager
because ServiceModuleManager
is derived from ModuleManager
which implement IModuleManager
. - The modules to be loaded by the application are acquired. In this example, they are specified in the file "modules.xml"
- The modules are registered. This calls into the virtual method
InitializeRegistrants
, which is overridden in ServiceModuleManager
and implements the first step of the initialization process. This is where modules that implement services can register those services, as well as locally save the service manager for use by those services. - Once all the module services have been registered, the bootstrapper tells the Service Manager to call
FinishInitialization
for all singletons registered in modules
public override void FinishedInitialization()
{
singletons.ForEach(kvp => kvp.Value.FinishedInitialization());
}
Only services registered as singletons get the FinishedInitialization
call. As mentioned in the Service Manager article, singletons are instantiated immediately and, because they exist, they can now complete whatever initialization (including calling other services) they require. Non-singleton services typically initialize themselves in their constructor.
This is a bit problematic if you have a service initialization that calls another service that hasn't finished its initialization. At some point, I might implement an initialization order process, but technically, because the services are initialized in the order of the module list, you can place dependencies higher up in the list.
Not bad for six lines of high level code!
Using the Bootstrapper
Using the bootstrapper is very simple:
using System;
namespace BootstrapDemo
{
static partial class Program
{
static void Main(string[] args)
{
InitializeBootstrap();
Bootstrap((e) => Console.WriteLine(e.Message));
}
}
}
A Couple Examples
Let's write a few services as modules to illustrate how this all works.
Clifton.AppConfigService
This is a very simple service that I always use that wraps .NET's ConfigurationManager
for obtaining connection strings and app settings:
using System.Configuration;
using Clifton.Core.ModuleManagement;
using Clifton.Core.ServiceInterfaces;
using Clifton.Core.ServiceManagement;
namespace Clifton.Cores.Services.AppConfigService
{
public class AppConfigModule : IModule
{
public void InitializeServices(IServiceManager serviceManager)
{
serviceManager.RegisterSingleton<IAppConfigService, ConfigService>();
}
}
public class ConfigService : ServiceBase, IAppConfigService
{
public virtual string GetConnectionString(string key)
{
return ConfigurationManager.ConnectionStrings[key].ConnectionString;
}
public virtual string GetValue(string key)
{
return ConfigurationManager.AppSettings[key];
}
}
}
There are always two parts:
- A class that implements
IModule
and registers the service - One or more classes that implement the service
We'll use an example app.config file:
="1.0"="utf-8"
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="myConnectionString" connectionString="some connection string"/>
</connectionStrings>
<appSettings>
<add key ="someKey" value="someKeyValue"/>
</appSettings>
</configuration>
And in modules.xml, we'll specify the Clifton.AppConfigService
:
="1.0"="utf-8"
<Modules>
<Module AssemblyName='Clifton.AppConfigService.dll'/>
</Modules>
A Cheat
The question though is, how do you get the module DLL (in this case, Clifton.AppConfigService.dll) into the bin\Debug folder of the application? I cheat by adding it as a reference to the application's references:

This is probably a bad practice because of course, the classes are now directly accessible to your application!
Using the Service
This is also very simple -- get the service from the Service Manager and start using it:
static void Main(string[] args)
{
InitializeBootstrap();
Bootstrap((e) => Console.WriteLine(e.Message));
IConfigService cfgSvc = serviceManager.Get<IConfigService>();
Console.WriteLine(cfgSvc.GetConnectionString("myConnectionString"));
Console.WriteLine(cfgSvc.GetValue("someKey"));
}

Notice that we're requesting an IConfigService
implementor. While we could specify an IAppConfigService
implementor, we'll see below why we reference the abstract
interface instead.
Clifton.EncryptedAppConfigService
In this example, we assume that your app.config is either completely encrypted, or not encrypted at all -- in other words, the service that obtains the values for us is exclusive, we use only one of them. This allows us to utilize the IConfigService
interface regardless of whether the concrete implementation simply returns unencrypted values, or decrypts them for us first. If we need to support both, then we would need to specify to the Service Manager which one we want: the concrete interface IAppConfigService
or IEncryptedAppConfigService
.
The encrypted app config service looks like this:
public class ConfigService : ServiceBase, IEncryptedAppConfigService
{
public virtual string GetConnectionString(string key)
{
string enc = ConfigurationManager.ConnectionStrings[key].ConnectionString;
return Decrypt(enc);
}
public virtual string GetValue(string key)
{
string enc = ConfigurationManager.AppSettings[key];
return Decrypt(enc);
}
protected string Decrypt(string enc)
{
return ServiceManager.Get<IAppConfigDecryption>().Decrypt(enc);
}
}
Notice that to decrypt the string
s in app.config, we use a service that must be provided by the application: IAppConfigDecryption
. We could implement this in another module, but in this example, I'll add it to the application directly.
The interface looks like this:
public interface IAppConfigDecryption : IService
{
string Password { get; set; }
string Salt { get; set; }
string Decrypt(string text);
}
And the implementation of the service, as a new module:
using Clifton.Core.ExtensionMethods;
using Clifton.Core.ModuleManagement;
using Clifton.Core.ServiceInterfaces;
using Clifton.Core.ServiceManagement;
namespace AppConfigDecryptionService
{
public class AppConfigDecryptionModule : IModule
{
public void InitializeServices(IServiceManager serviceManager)
{
serviceManager.RegisterSingleton<IAppConfigDecryption, AppConfigDecryptionService>(d =>
{
d.Password = "somepassword";
d.Salt = "somesalt";
});
}
}
public class AppConfigDecryptionService : ServiceBase, IAppConfigDecryption
{
public string Password { get; set; }
public string Salt { get; set; }
public string Decrypt(string text)
{
return text.Decrypt(Password, Salt);
}
}
}
Obviously (I hope), you would want to get the password and salt from some secure location so the string
s aren't easily obtained by decompiling the DLL, or you would initialize them in your application.
Now, I really like extension methods, so the real implementation is there (BTW, I'm not saying this is the best implementation, actually this is something I found on StackOverflow, haha):
public static string Decrypt(this string base64, string password, string salt)
{
string decryptedBytes = null;
byte[] saltBytes = Encoding.ASCII.GetBytes(salt);
byte[] passwordBytes = Encoding.ASCII.GetBytes(password);
byte[] decryptBytes = base64.FromBase64();
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(decryptBytes, 0, decryptBytes.Length);
cs.Close();
}
decryptedBytes = Encoding.Default.GetString(ms.ToArray());
}
}
return decryptedBytes;
}
We can now provide encrypted values in the app.config file:
="1.0"="utf-8"
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="myConnectionString"
connectionString="D+560OKzdaeBle1VHcKc+JyAWgRkVNTQxu/t7K5jSUo="/>
</connectionStrings>
<appSettings>
<add key ="someKey" value="JggSd0i52WcOEERjBTQR+g=="/>
</appSettings>
</configuration>
And we also modify the modules.xml file to use the encrypted app config service and, in addition, specify the decryption service we want:
="1.0"="utf-8"
<Modules>
<Module AssemblyName='Clifton.EncryptedAppConfigService.dll'/>
<Module AssemblyName='AppConfigDecryptionService.dll'/>
</Modules>
Now we run the app, and get the same result, but now the string
s are unencrypted:


Notice how we didn't have to change the application code at all. This works because the application can use the app config service, treated an exclusive service (whether plain text or encrypted), with the abstract interface IConfigService
. If we were supporting both plain text and encrypted values, we'd have to acquire the correct service using IAppConfigService
(for plain-text values) or IEncryptedAppConfigService
(for encrypted values.)
Interface Hell
If you thought DLL hell was bad, one of the things that starts to happen with a module/service-based implementation is the potential for interface hell. Each service implements an interface. Where do you keep them? How do you organize them? Both the module implementing the service and the application using the service require a reference to the interface. I usually organize the interfaces in two separate projects:
Clifton.Core.ServiceInterface
-- This is for services that are provided in my core library. [MyAppServiceInterfaces]
-- This is for application-specific services.
Conclusion
Modules and services are a great way to implement dependency inversion, and the above example hopefully illustrated how powerful this architecture is. However, I still think it is not abstract enough, for the simple reason that the application still needs references to either abstract or concrete interfaces from which to obtain the implementing service.
This will be addressed in the next article, where I introduce the semantic publisher-subscriber. That will not be light-weight!
History
- 25th August, 2016: Initial version