Dependency Injection: Modularize Your Services and Applications
There are many different patterns for objected oriented development that have emerged over the years. More often than not I will stumble across one that we are inadvertently using in our services or applications at REJIS. Most recently I wrote about an approach we use when we need a service client that will take data from a service and transform or adapt it to another format (database, xml model, fixed length file, etc). At the core of that pattern is dependency injection.
Martin Fowler has an excellent (and lengthy) discussion on Inversion of Control and Dependency Injection which is well worth the read. Wikipedia defines dependency injection as "…the process of supplying an external dependency to a software component. It is a specific form of inversion of control where the concern being inverted is the process of obtaining the needed dependency."
For example lets say that you have an interface, IMyInterface, which Class A depends on for some functionality. Class B and C both implement IMyInterface making them substitutable for Class A’s dependency. At runtime, through configuration or programmatically, you can decide which concrete class (B or C) for Class A to use. Rather than having Class A directly depend on Class B or C you reduce coupling by relying on the interface and then you inject this dependency at runtime.
Interfaces represent one of the best possible abstractions available to an object oriented programmer. The benefits to using interfaces and dependency injection are numerous:
- By using an interface you have defined a contract of expected behavior for implementers.
- Interfaces reduce coupling, allowing for independent unit testing, and multiple implementations of the interface.
- Code becomes simplified and focused by reducing branching logic and overall lines of code.
- Changes to the application can potentially be applied at runtime (if this is a web app, windows apps would require closing and reopening) and would not require a recompile of the entire application (if the new implementing class is in another assembly only that assembly needs to be compiled).
- The application will be more "plug and play" and allow for adding or removing functionality as needed.
MSDN has an article that discusses Dependency Injection in depth and how it relates to a few other Creational patterns such as factory. There are a number of Inversion of Control (IOC) frameworks that assist with dependency injection that use the notion of containers which help manage the lifecycle of objects and apply configuration settings to modify behaviors at runtime. For a list of open source IOC frameworks check out this post by Scott Hanselman.
Dependency Injection Example – Supporting Multiple Authentication Methods
I have created a sample project which I will use to illustrate the flexibility of this approach. The sample covers a very common scenario where we must support multiple authentication methods to allow flexibility for our customers. We’ll look at a traditional approach and then an updated approach that will use Dependency Injection.
At the core of this example is the IAuthenticator Interface and the User Class. Both are very simple for examples sake.
Public Interface IAuthenticator Function Authenticate(ByVal userName As String, _ ByVal password As String) As User End Interface
Public Class User Public Name As String Public AuthenticationType As String Public IsAuthenticated As Boolean End Class
The customer requested that the application first be built to support Database Authentication. Because our architect was thinking ahead and at least forced us to use an interface we will implement that interface in our DatabaseAuthenticator class.
Public Class DatabaseAuthenticator Implements IAuthenticator Public Function Authenticate(ByVal userName As String, _ ByVal password As String) As User Implements IAuthenticator.Authenticate Dim user As New User user.AuthenticationType = "DatabaseAuthentication" 'Simulate DB authentication If userName = "Test" AndAlso password = "Testing!" Then user.IsAuthenticated = True user.Name = userName 'else 'could throw security exception or any other action here. End If Return user End Function End Class
To test our authentication we’ll go "old school" with a little console program action. The console program will display a username prompt and password prompt then it will invoke our IAuthenticatorInstance to perform the authentication.
Let’s first look at life without dependency injection:
Module Module2 Sub Main() Dim authenticator As Authenticator.IAuthenticator Select Case My.Settings.AuthenticationType Case "Database" authenticator = New Authenticator.DatabaseAuthenticator Case Else Throw New ArgumentException("Invalid AuthenticationType!") End Select Dim userName As String Dim password As String Dim user As Authenticator.User Console.WriteLine("Username: ") userName = Console.ReadLine Console.WriteLine("Password: ") password = Console.ReadLine Console.Clear() user = authenticator.Authenticate(userName, password) If user.IsAuthenticated Then Console.WriteLine(String.Format("User authenticated via {0}!", _ user.AuthenticationType)) Else Console.WriteLine("User authentication failed!") End If Console.ReadLine() End Sub End Module
In order for Module2 to work we’ve had to hard-code a string value in our case statement to help us decide the type we want to create. From there we instantiate the appropriate class manually. This works fine; it will run and there are no problems with it…until you need a new authentication method. Then you must go back and add a new class that implements IAuthenticator, add a branch to the Case statement and then instantiate your new class. Of course you could do this without interfaces as well…but I digress.
On to life with dependency injection:
Module Module1 Sub Main() Dim authenticatorType As System.Type = _ Type.GetType(My.Settings.AuthenticatorType) Dim authenticator As Authenticator.IAuthenticator = _ Activator.CreateInstance(authenticatorType) Dim userName As String Dim password As String Dim user As Authenticator.User Console.WriteLine("Username: ") userName = Console.ReadLine Console.WriteLine("Password: ") password = Console.ReadLine Console.Clear() user = authenticator.Authenticate(userName, password) If user.IsAuthenticated Then Console.WriteLine(String.Format("User authenticated via {0}!", _ user.AuthenticationType)) Else Console.WriteLine("User authentication failed!") End If Console.ReadLine() End Sub End Module
You probably noticed the first line where we dim up the authenticator variable by calling Activator.CreateInstance and passing it a type variable which I resolved using the Type.GetType method. Type.GetType(string) returns the System.Type which we can then pass to activator to get our instance created.
Our settings file contains this section:
<applicationSettings> <DepdencyInjectionConsole.My.MySettings> <setting name="AuthenticatorType" serializeAs="String"> <value>Authenticator.DatabaseAuthenticator, Authenticator</value> </setting> </DepdencyInjectionConsole.My.MySettings> </applicationSettings>
The notation I used for the type information is the standard "Class, Assembly". Note that I am not using strong names so it is not necessary to include version and public key information. Also note that none of the code in Module 1 references the concrete class DatabaseAuthenticator.
You may have also noticed that, including whitespace, Module1 has 29 lines versus Module2’s 36 lines of code (excluding line continuations). In our simple example 7 lines isn’t much but in complex systems that factor could be easily multiplied by 10 or more.
The code that was written for Module1 has no dependencies other than it has to know about Authenticator.IAuthenticator. The true dependency is injected at runtime. If we try to use a type in the AuthenticatorType setting that the system cannot resolve then our program will blow up.
Which leads us to the paradigm shift; you have to be aware of your operating environment and ensure that the assemblies you need are available in the GAC or in your applications executing or bin folder (for web apps and IIS hosted services). This does not negate managing your dependent assemblies but it shifts it from something necessary throughout development to something that could potentially only happen during deployment. Because the code only depends on the Interface IAuthenticator two completely different teams could be coding the main module and an IAuthenticator implementation. Development for Module2 is more susceptible to breaking changes because of direct dependencies on the classes that implement IAuthenticator.
Adding Another Authenticator Implementation
The customer you wrote this system for now wants to add authentication that will read an XML document for user accounts. We create a new assembly called CustomAuthenticator and our new class looks like this:
Public Class XmlAuthenticator Implements Authenticator.IAuthenticator Private Shared authenticationStore As Xml.Linq.XDocument Sub New() authenticationStore = _ Xml.Linq.XDocument.Load("C:\Temp\AuthenticationStore.xml") End Sub Public Function Authenticate(ByVal userName As String, _ ByVal password As String) As Authenticator.User _ Implements Authenticator.IAuthenticator.Authenticate Dim user As New Authenticator.User Dim userQuery = From u In authenticationStore...<users>...<user> _ Where u...<name>.Value = userName _ And u...<password>.Value = password _ Select u user.AuthenticationType = "XmlAuthentication" user.IsAuthenticated = userQuery.Count = 1 If user.IsAuthenticated Then user.Name = userName End If Return user End Function End Class
The XmlAuthenticator uses an XML file to store user accounts and loads in its constructor. Using LINQ we query the XML document to find if our user exists. This class resides in a completely separate assembly.
If you are adept at reading LINQ XML Queries you can guess the documents structure:
<?xml version="1.0" encoding="utf-8" ?> <users> <user> <name>Test</name> <password>Testing!</password> </user> </users>
Adding XmlAuthenticator to Module2 (Anti-Dependency Injection Module)
In order to implement this in Module2 we need to add a branch to our case statement:
Case "XML" authenticator = New CustomAuthenticator.XmlAuthenticator
Total cost – two lines of code, another hard-coded string, someone has to update the config file and deploy the new assembly.
Adding XmlAuthenticator to Module1 (Pro-Dependency Injection Module)
In order to implement this in Module1 we need to…do nothing. Not programmatically anyhow, but we do need to update our AuthenticatorTypeValue in the app.config.
Here’s the new config:
<applicationSettings> <DepdencyInjectionConsole.My.MySettings> <setting name="AuthenticatorType" serializeAs="String"> <value>CustomAuthenticator.XmlAuthenticator, CustomAuthenticator</value> </setting> </DepdencyInjectionConsole.My.MySettings> </applicationSettings>
Total cost – zero lines of code and someone has to update a config file and deploy the new assembly. Less work and less code to maintain. Ladies and gentlemen I think we have a winner here!
This is a very simple example that hopefully illustrated what you can achieve using dependency injection. The code is simply for illustration and I do not recommend or condone anyone using for their authentication components (nor will I be liable for anything you do with this)!
What are some real world ways we use this?
For service clients where we are responsible for retrieving data from a service and doing something with it we use dependency injection for what we our "adapters". The adapters take a known type from a service and do whatever is needed; store data in a database, flat file, or another XML format.
For a service we are developing on a statewide data sharing project dependency injection is being used to allow for a custom implementation of activities within that service so that the service can be deployed to another geographical region in the state and customized via configuration. The major functionality points were identified and abstracted with interfaces so the functionality could be implemented in different ways for the two regions. This also allows a separate code base that the other region can maintain and update as needed. The core functionality is completely similar between each region and should require no modification except any bugs that may surface.
We will also use this approach on another service to allow a custom "path" for a document that we will receive from an external source. We have cases where, depending on what external partner sends us information, we need to do multiple things with it and this can vary from partner to partner. This last example may require a more sophisticated method so I am leaning towards the Unity Framework from Microsoft as we already use Enterprise Library extensively in our services.
Another Tool in the Toolbox
The best way to look at all of these object-oriented approaches is that they are tools in your toolbox. I will likely not use dependency injection everywhere. But in the work I do with services we need this pattern more often than not. Hopefully this gives you enough insight to decide if you may be able to benefit from using it in your own applications.
Comments are closed.

