Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C#

The Clifton Method - Part III

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
25 Aug 2016CPOL6 min read 11.3K   142   7   2
Bootstrapping with Module Manager and Service Manager
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:

C#
public interface IModule
{
  void InitializeServices(IServiceManager serviceManager);
}

We can see this being applied in the ServiceModuleManager, which overrides InitializeRegistrants:

C#
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()
    {
    }

    /// <summary>
    /// Initialize each registrant by passing in the service manager.
    /// This allows the module to register the services it provides.
    /// </summary>
    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:

C#
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);
      }
    }

    /// <summary>
    /// Return the list of assembly names specified in the XML file so that
    /// we know what assemblies are considered modules as part of the application.
    /// </summary>
    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);
    }

    /// <summary>
    /// Returns the list of modules specified in the XML document so we know what
    /// modules to instantiate.
    /// </summary>
    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...

C#
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...

C#
IModuleManager moduleMgr = (IModuleManager)serviceManager.Get<IServiceModuleManager>();
List<AssemblyFileName> modules = GetModuleList(XmlFileName.Create("modules.xml"));
moduleMgr.RegisterModules(modules);
serviceManager.FinishedInitialization();
  1. 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.
  2. The modules to be loaded by the application are acquired. In this example, they are specified in the file "modules.xml"
  3. 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.
  4. Once all the module services have been registered, the bootstrapper tells the Service Manager to call FinishInitialization for all singletons registered in modules
C#
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:

C#
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:

C#
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:

  1. A class that implements IModule and registers the service
  2. One or more classes that implement the service

We'll use an example app.config file:

XML
<?xml version="1.0" encoding="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:

XML
<?xml version="1.0" encoding="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:

C#
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:

C#
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 strings 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:

C#
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:

C#
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 strings 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):

C#
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; // Cipher Block Chaining.

      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:

XML
<?xml version="1.0" encoding="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:

XML
<?xml version="1.0" encoding="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 strings 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
QuestionMissing a private set? Pin
Paulo Zemek25-Aug-16 9:00
mvaPaulo Zemek25-Aug-16 9:00 
I just started reading the article and I noticed this:
C#
public IServiceManager ServiceManager { get; set; }

Considering the ServiceManager is set in the constructor, shouldn't this be a get; private set; ?
AnswerRe: Missing a private set? Pin
Marc Clifton25-Aug-16 14:27
mvaMarc Clifton25-Aug-16 14:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.