Inversion of Control
The first thing to consider when planning an advanced architecture is support of extensions. Many platforms support tools and other extras, as independently compiled components. In general this is only possible when the references are soft coded.
Architecture Overview
To build an inversion of control framework from the ground up, we`ll focus on three modules - The main application, A library with helper utilities, and a component that we`ll use in the application without referencing it directly during the compilation.
We will use the standard app.config file to soft code the information about the component, divided in two parts - the assembly name that we need to build the path to the DLL file and the class name for instantiating the component at run-time using reflection. This is part of the first module - the application.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="IComponentModule" value="SampleComponent.dll" />
<add key="IComponentClass" value="SampleComponent.SampleComponent" />
</appSettings>
</configuration>
And a very simplified interface of the component. This is part of the common library module:
public interface IComponent
{
string GetResult();
}
Basic Dependency Injection Framework
First we`ll implement the framework that handles the dependency injection functionality, and next we`ll build the whole sample application. Basically we need a couple of methods in a class Tools (that could be a singleton). This class is supposed to be in the common library module as well:
The first method is a generic approach to read values from a standard App.config file:
private static T GetCfgValue<T>(string key)
{
try
{
var value = ConfigurationManager.AppSettings[key];
return (T)Convert.ChangeType(value, typeof(T));
}
catch
{
return default(T);
}
}
The next part is loading the assembly:
private static Assembly GetModuleByType(Type interfaceType)
{
string toolName = interfaceType.Name;
string assemblyPath = Assembly.GetExecutingAssembly().Location;
string dir = Path.GetDirectoryName(assemblyPath);
string file = GetCfgValue<string>(toolName + "Module");
string modulePath = Path.Combine(dir, file);
return Assembly.LoadFile(modulePath);
}
And getting the component type:
private static Type GetType<T>()
{
string toolName = typeof(T).Name;
string CfgTypeKey = toolName + "Class";
var module = GetModuleByType(typeof(T));
return module.GetType(GetCfgValue<string>(CfgTypeKey));
}
And the last thing is the only public method in our Tools singleton, that provides the API for instancing components dynamically, by a given interface:
public static T GetInstance<T>()
{
var type = GetType<T>();
return (T)Activator.CreateInstance(type);
}
The Independently Compiled Component
So far we have the app.config file from the application, and the common library module that contains the interface of the component and the basic inversion of control framework. Now we`ll implement the component with an API specified in the interface, which has a single method for brevity.
public class SampleComponent : IComponent
{
public string GetResult()
{
return "This is a sample result";
}
}
Putting it all together
At this point we have everything we need to instantiate the component, from the main application that has no reference to it during the compilation at all. However creating an instance of that class is as easy as:
class Program
{
static void Main(string[] args)
{
var instance = Tools.GetInstance<IComponent>();
Console.WriteLine(instance.GetResult());
}
}
Once we have a framework for instancing classes from different modules by a given interface, we can easily add a setup utility to our application to allow switching between different implementations of different application plugins.
The next steps
From this point we can look at even higher level of abstraction - loading modules from the cloud, to allow updates which do not require patching every installation of the software and more benefits in general. We will cover this topic later too.