Note: To access the latest sources for the LinFu v2.0 Framework directly from the SVN, click here.
Introduction
In this first part of the series, I'll show you how you can use attributes with LinFu.IoC to add your custom services to the container, and how you can use LinFu.IOC to quickly add dependency injection capabilities to your application in the fewest lines of code possible. I'll also show you how you can use LinFu's IOC container to instantiate and customize nearly any .NET type, including primitive types and types that your application does not own.
Overview
A Change in Style?
In contrast to my previous articles on LinFu, the goal of each one of the articles in this series is to focus more on the practical use of LinFu and dependency injection rather than go deep into the theory behind Inversion of Control, and dependency injection itself. I won't bore you with theoretical explanations--instead, we'll dive straight into the code, and I'll keep these articles as short and informative as possible.
Why Use LinFu?
Developers New to Inversion of Control
One of the barriers to understanding how Inversion of Control works is that a lot of Inversion of Control frameworks are so massive (or sparsely documented, or both) that it's hard for someone new to step through the said framework code and figure out how it all works. That's not the case with LinFu. Every class, method, and property in LinFu.IOC v2.0 (regardless of whether or not it is public, private, or internal) is heavily-documented, and the code is simple enough that you don't have to worry about burying yourself in someone else's terminology. Furthermore, LinFu.IOC has an almost flat class hierarchy, meaning that you won't have to sift through layers of inheritance to see exactly what is happening in the code, and if that isn't enough, the Unit Test cases will show you exactly how to use each feature, so you can be sure that everything will work as advertised.
Existing IOC Container Users
If you're already using another IOC container such as Castle, Ninject, StructureMap, or AutoFac (and even Spring.NET), you might be wondering how LinFu.IOC compares to the other containers. You can think of LinFu as the "Swiss Army knife" of IOC containers. It is not only an IOC container (per se), it is also a general implementation of the Factory Pattern on steroids, and in this series, I'll show you how truly useful LinFu.IOC can be. LinFu's IOC container can create and configure nearly any type of object, and for those who are already satisfied with the current IOC framework they're using, that feature should at least pique your interest, if only for the sake of your own curiosity.
Note: To see how LinFu stacks up compared to the other .NET Dependency Injection/Inversion of Control frameworks, click here.
The Quick and Dirty Overview
In a nutshell, LinFu's IOC container is simple enough that you can learn it in five minutes, and it's also flexible enough that it supports various coding styles without forcing you to follow a specific style of development. It supports the following features:
- Additional Service Arguments. Although seemingly trivial, this feature allows you to pass additional arguments to LinFu's container when you instantiate any given service. Many DI/IOC containers pass these arguments directly to the implementing type's constructor; LinFu, in contrast, allows you to take those arguments and add custom commands to the container as if those commands were part of LinFu's container in the first place. For example, you can use this to do things like turn LinFu.IOC into a controller in an MVC architecture, or you can use these same features and turn it into a simple calculator--yes, it's that flexible.
- Available Service Enumeration. LinFu is the only IOC container (so far) that can actually give you a list of services that currently reside in the container. You can actually query a container for the services that it supports, and you can even instantiate any one (or all) of them, if necessary.
- Automatic Field, Method, and Property Injection. Similar to its Ninja-like cousin, LinFu supports injecting service dependencies into fields, properties, and methods that are marked with a particular custom attribute. If LinFu's built-in
InjectAttribute
class doesn't suit your needs, you can always substitute it with your own custom attribute to keep LinFu separate from your domain model.
- Contextual Binding. Like other containers, LinFu allows you to dynamically determine which service implementation should be provided for a given service type. LinFu allows you to bind a particular service instance to any property, method, field, or constructor. However, unlike other containers, LinFu supports a special form of contextual binding that allows you to decide which concrete service type should be created depending on the additional parameters passed to LinFu.IOC during a service request. It's an incredibly useful feature, and this is one you really have to see to believe.
- Constructor Injection. LinFu can automatically inspect a given type and choose the constructor with the most resolvable parameter types. This means that if you have a constructor and if one or more of its parameters is a service type that resides inside the container at the time you request that service type, LinFu will be able to fill in all the constructor arguments and use that particular constructor to instantiate your service type. In addition, LinFu supports covariant constructor arguments, meaning that it's smart enough to determine which services should be injected into the constructor arguments even if those arguments don't exactly match the services currently available in the container. It can also distinguish between a nearly infinite number of constructors declared on a target type, so you'll almost never have to worry about running into constructor ambiguity issues when you create your services.
- Declarative/Attribute-Based Registration. A single attribute declaration is all LinFu needs to inject your services into the container. With LinFu.IOC's attribute support, you don't have to worry about maintaining a large XML file or updating your binding code to keep your service dependencies up to date. All you have to do is drop your updated binaries into a target directory, and LinFu will make the application practically update itself instantaneously.
- Extensible Instantiation Pipeline. If you love extending frameworks like I do, then you're going to love all the extension points LinFu.IOC has to offer for creating service instances. LinFu lets you control everything from determining the factory object that will instantiate your services to adding custom steps to the pipeline so you can modify, intercept, and even replace the service instances that come out of the container. The best part about all this is that like the attributed service types, all you need to do to extend the container is "drop in" the extension DLL into the target container directory and the container will automatically add your extension to the container itself. It can't get any more simple than that.
- Fluent Registration. If you're a fan of using explicit interfaces to describe which dependencies the container should use, then LinFu.IOC might just be what you need. LinFu allows you to use fluent interfaces so that you can keep your container binding code all in one place without sacrificing the readability in your code.
- Factory Lambda Support. Like AutoFac, LinFu takes advantage of C# 3.0's lambda syntax to make it easier for you to register your service instances without having to create the actual service instances themselves. Once you plug in your own lambda factory methods into the container, the container will behave as if your factory method was always part of the container in the first place, and in this series, you'll see just how useful these factory lambdas can be.
- IEnumerable Service Injection for Fields, Methods, Properties, and Constructors. LinFu also has the ability to automatically inject a list of currently available services of a specific type into a type's constructors, methods, properties, and fields. You can even instantiate all of them at once with a single lambda expression.
- Interceptors. LinFu.IOC supports service interception right out of the box, and LinFu.Proxy (a.k.a. LinFu.DynamicProxy v2.0) has been fully integrated into the container so that you can define your own "drop-in" service interceptors using a single attribute declaration. Needless to say, this feature makes it easy to transparently extend your service instances with other features such as transaction management and logging, all without having to modify your concrete service implementations.
- Open Generics Injection and Registration. LinFu allows you to reduce the number of services that reside in a container by letting you register generic type definitions to handle a whole family of generic types. For example, you can tell the container to instantiate a
List<T>
type every time a user tries to instantiate an IEnumerable<T>
service instance, and LinFu.IOC will automatically provide that list for you no matter what type T
might be.
- Scoped Objects. LinFu can automatically keep track and dispose of any
IDisposable
instance that it creates within a given using
block or scope.
- Support for Instantiating POCO objects, Primitive Types, and Third-Party Objects. LinFu can create almost any type, regardless if that type is a primitive type (such as
System.Int32
), a string
, or a reference type that you do not control (such as a .NET BCL class or an NHibernate ISession
object). You can literally customize the construction of any type to your heart's content, and in this article, I'll show you how you can use these features to turn LinFu.IOC into a configuration string reader.
- Unregistered Resolution. LinFu can also perform dependency injection on types that don't reside in the container, and that means that you can instantiate types with the same automatic injection semantics that are typically reserved for types that are already registered in the container itself. In layman's terms, this means that if you have a type that can be injected, LinFu.IOC has the power to inject it, regardless of where that object resides.
- XCOPY Deployment. One of LinFu.IOC's most useful features is that you can change its configuration simply by "dropping in" any additional DLLs into a target directory of your choice and telling LinFu.IOC to rescan that directory. A few lines of code (plus an XCOPY command) is all you need to extend the container with your own service implementations, interceptors, factories, and container plug-ins, and you'll be able to do this without recompiling your application.
As you can see, we have a lot of ground to cover, but you don't need to know how to use all these features in order to use LinFu. Each article in the series is going to cover at least one of these features that I mentioned above, and in this particular article, I'll show you how you can use attributes with LinFu to get your application up and running in no time. LinFu.IOC is packed with a whole slew of goodies, and this holiday season, it's time to give back to all the other developers out there on CodeProject that have made this site the great place that it is--and with that in mind, let's get started!
Background
As promised, this article won't talk much about theory, but I certainly won't leave my readers in the dark without some resources to help you get up to speed on the theory behind Inversion of Control and dependency injection. For those of you who are new to Inversion of Control, here are a few resources to get you started:
- Martin Fowler on Dependency Injection. This page teaches the basics of Inversion of Control containers, service locators, and using interfaces to make your application easier to maintain. It even shows you how to perform manual constructor injection, and knowing it is going to come in handy once you start dealing with LinFu.IOC's automatic constructor injection features.
- My Article on Inversion of Control and Dependency Injection. The article talks about LinFu.IOC's predecessor,
Simple.IOC
, and explains some of the basic elements of an Inversion of Control container. Once you get familiar with how the attributes are being used in that article, then you'll have no problem understanding the concepts that I'll be talking about when it comes to LinFu.IOC.
- Dependency Injection Books on Amazon. If you have deep pockets and if you want to further dive headlong into understanding Inversion of Control, then this might be the place for you. If your pockets aren't so deep (like mine), then the used books section has the same books for half the price, and this is the place to be if you're on a tight budget.
Using the Code
In general, there are only a few things that you need to know to use the basic functions of LinFu.IOC. They are:
All Roads Lead to the ServiceContainer Class
The ServiceContainer
class is the heart of LinFu.IOC, and it's the only class that you have to create to get started:
var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
Aside from the LoadFrom
method call, the code above is unequivocally self-explanatory. The LoadFrom
method tells the container to configure itself using the contents of all the DLL assemblies in the application base directory. Believe it or not, this is the only method call that you need to make to get the container to load all your services. Now that we're done going over the basics of loading LinFu's container, I'll show you how you can use it to create types in the next section.
Instantiating Your Types
Getting Some Service
The ServiceContainer.GetService()
method is the only method you'll ever need to use to create types with LinFu.IOC. There are several overloads for this particular method, but for now, you only need to concern yourself with the following overloads for the GetService
method:
Strong and Weakly-Typed GetService Methods
For your convenience, LinFu.IOC provides untyped and strongly-typed overloads for the GetService
method. If I wanted to instantiate a service type named ISomeService
(or even a primitive type) for example, the following overloaded method calls perform exactly the same function:
var myService = container.GetService<ISomeService>();
myService = (ISomeService)container.GetService(typeof(ISomeService));
Intellisense, of course, will intelligently sense that there are far more generic and non-generic overloads of the GetService
method available for you to use than the two methods calls that I showed you in the above examples. However, if you're only going to use LinFu.IOC to create simple objects or services, those two methods will be the only versions of GetService
that you will ever need.
Named Services
Unfortunately, a developer's life is rarely ever simple, and there's a good chance that you'll run into different scenarios where you need to create two different types depending on the name of the service that you request from the container. For example, let's suppose that you wanted the container to give you the values of two separate configuration strings. Here's how you do it:
var serviceName = "OracleConnectionString";
var connectionString = container.GetService<string>(serviceName);
serviceName = "Sql2k5ConnectionString";
connectionString = container.GetService<string>(serviceName);
As you can see from the example above, I actually used the serviceName
variable to distinguish between two service instances of the same type (in this case, I used a string
). Again, it's very straightforward, and the important thing to remember here is that you can use service names with LinFu.IOC to specify different configurations. The example above might seem trivial, given that I used a string
type, but another important thing to remember is that you can do the same thing to any other .NET type, regardless of whether or not it is a value or a reference type. Most major .NET DI/IOC frameworks support named services in one fashion or the other, and LinFu is no exception.
Additional Service Arguments
There might be times where you might want to pass a certain set of arguments to the container so that the container can take those same arguments and pass it along to the constructor of the concrete service type. For example, let's suppose that I have a service named IGreeter
with a concrete Greeter
implementation:
public class Greeter : IGreeter
{
private readonly string _name;
public Greeter(string name)
{
_name = name;
}
public void Greet()
{
Console.WriteLine("Hello, {0}!", _name);
}
}
Since the Greeter
class itself doesn't have a default constructor, there doesn't seem to be an immediate way to instantiate the IGreeter
service type. This is where LinFu's support for additional parameters comes in handy:
var myName = "Me";
var greeter = container.GetService<IGreeter>(null, myName);
greeter.Greet();
In the example above, LinFu.IOC uses the additional myName
argument to fill in the missing parameter in the constructor arguments. Whenever you try to instantiate a service type with missing constructor arguments, LinFu will use the additional arguments given to the GetService
method call and match those arguments to the constructor with the most compatible method signature. What makes this even more interesting is the fact that LinFu can distinguish between any number of constructor signatures regardless of the number of parameters or the parameter types that might be embedded in a particular constructor signature. In fact, LinFu's constructor resolution accuracy actually goes up with every additional parameter you pass to the GetService
call. In other words, you'll never have to worry about getting an incompatible constructor call with LinFu. It just works.
Registering Your Types
Manual Registration
LinFu.IOC wouldn't be useful if it wasn't at least symmetrical, and for every GetService
method overload, there is an equal and opposite AddService
method that can register your types with the container itself. Here's how you can use the AddService
method to register the unnamed ISomeService
type from the previous example:
var someServiceInstance = new SomeService();
container.AddService<ISomeService>(someServiceInstance);
container.AddService(typeof(ISomeService), someService);
If you need to use named services, here's how you can register those connection strings with the container:
var connectionString = "Driver={Oracle in OraHome92};
Server=myServerAddress;Dbq=myDataBase;Uid=myUsername;Pwd=myPassword;";
container.AddService("OracleConnectionString", connectionString);
connectionString = "Data Source=myServerAddress;
Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;";
container.AddService("SQL2k5ConnectionString", connectionString);
There are quite a few more overloaded versions of the AddService
method, of course, but the ones that I used in the examples above should be all you need to register your types manually. However, since the purpose of this article is to show you how to do automatic registration with LinFu, I'll show you how to forgo using the AddService
method altogether in the next section.
Having Your MEF, and Eating it Too
There are other quasi-Inversion of Control frameworks that allow you to essentially "export" your types from a given assembly, and their corresponding container implementations will, in turn, take all these "exported" dependencies and bind them all together at runtime. These dependencies (a.k.a. service types and their concrete implementations) are all defined using custom attributes which tell the container how to bind the concrete service types with the type being "exported" (that is, the service type that is being implemented by the concrete type). In other words, they use attributes to bind your code together, and I'll show you how LinFu can do the same thing without forcing you to wade through any new jargon or terminology. With that in mind, here's how you can use attributes with LinFu:
What They 'Export', I Shall Implement
LinFu.IOC can register each one of your types into the container using a single [Implements]
attribute declaration. For example, here's how you can use the [Implements]
attribute declaration to register the Greeter
class to implement the IGreeter
service type:
[Implements(typeof(IGreeter))]
public class Greeter : IGreeter
{
private readonly string _name;
public Greeter(string name)
{
_name = name;
}
public void Greet()
{
Console.WriteLine("Hello, {0}!", _name);
}
}
Loading Your Services into the Container
Assuming that your Greeter
class is located in an assembly named Greeter.dll and that assembly is located in the application directory, this is all you need to do to add the IGreeter
dependency to the container:
container.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "Greeter.dll");
The code pretty much speaks for itself. At runtime, LinFu.IOC will scan the target directory for your dependencies and automatically add them to the container. It's really that simple.
Registering Primitive and Third-Party Types
Although the AddService
method allows you to register any .NET type instance (including primitive types), there might be times where you need to have more control about how a type is constructed. For example, you might need to tell LinFu.IOC to pull specific strings out of a config file or the system Registry; or, there might even be times when you want LinFu to instantiate (and thus customize) a specific type of object (such as an NHibernate ISession
object) but you don't have the luxury of defining the [Implements]
attribute on that type since you don't own the source code for that library. In such cases, we're going to need something more robust, and that's where LinFu.IOC's custom factories fit into the picture.
Custom Factories
LinFu.IOC allows you to create your services by providing your own implementation of the IFactory
interface. Here's what it looks like:
public interface IFactory
{
object CreateInstance(IFactoryRequest request);
}
The IFactoryRequest
interface, in turn, is defined as:
public interface IFactoryRequest
{
IServiceContainer Container { get; set; }
string ServiceName { get; }
Type ServiceType { get; }
object[] Arguments { get; }
}
LinFu.IOC uses custom IFactory
instances internally to instantiate every service type available in the container. The IFactoryRequest
interface describes the context of the service request, and all you have to do to create primitive types (or third party types) is to add your own custom IFactory
implementation to the container, parse the request itself, and return your new custom service instance in the CreateInstance
method. For example, let's suppose that I wanted to make a ConfigurationStringFactory
that pulls strings out of an app.config file.
The IFactory
implementation would be:
[Factory(typeof(string))]
public class ConfigurationStringFactory : IFactory
{
public object CreateInstance(IFactoryRequest request)
{
var keyName = request.ServiceName;
return ConfigurationManager.AppSettings[keyName];
}
}
Here's the client code that uses GetService
to get a particular configuration string out of the config file:
var keyName = "key1";
var keyValue = container.GetService<string>(keyName);
The [Factory]
attribute declaration in the examples above will tell LinFu.IOC that the ConfigurationStringFactory
is capable of instantiating string
types. Once that factory has been automatically loaded into the container and the GetService
method has been called, the container, in turn, will call ConfigurationStringFactory.CreateInstance
and return the custom configuration string. The ConfigurationStringFactory
is wholly responsible for how the configuration strings are created, and by extension, the same principle applies to any .NET type you create with your own custom IFactory
instances. LinFu.IOC's custom factories allow you to decide which services to create as well as decide how a service type should be created. Creating value types isn't an issue because LinFu makes no distinction between creating value types and reference types. No matter which type you decide to use, the process for creating any type using LinFu.IOC is one and the same, and that's one of the reasons that makes LinFu so flexible.
Additional Parameters Galore
While the ServiceName
and ServiceType
properties in the IFactoryRequest
interface are self-explanatory, the Container
and Arguments
properties need a bit more explanation. The Container
property holds a reference to the actual container that made the service request, and the Arguments
property holds the additional arguments given to the GetService
method once the service request was made. In other words, the Container
property allows custom factories to use the services provided by their host container, as well as read the additional parameters that were passed in during the GetService
request. This allows you to do some pretty interesting things that would be difficult to do with other containers. For example, I can turn LinFu.IOC's container into a "quasi-calculator" of sorts by providing a custom factory named AdditionFactory
which takes two integers and adds them together:
[Factory(typeof(int), ServiceName="Add")]
public class AdditionFactory : IFactory
{
public object CreateInstance(IFactoryRequest request)
{
int a = (int)request.Arguments[0];
int b = (int)request.Arguments[1];
return a + b;
}
}
The client code would look something like this:
int result = container.GetService<int>("Add", 1, 1);
Needless to say, the amount of flexibility that LinFu's additional argument support provides can be quite staggering--but this gets even better. Since the IFactoryRequest.Container
property is exposed to each and every IFactory
implementation, you'll be able to access the host container along with all the other custom factories that have been loaded into that same container at runtime. The services provided by other factories inside the same container will already be available to you without having to know how those services are being created. Every IFactory
instance is isolated from all the other factories in the container. What makes this interesting, however, is the fact that each one of these factories can access each other's services through the Container
property as if those factories were never isolated in the first place. In layman's terms, this means that you can add multiple factories to LinFu.IOC, and every one of those factories will act as one--and that brings us to the last section: factory layering.
Factory Layering, Ad Infinitum
Having all IFactory
instances isolated from each other and yet still accessible through the IFactoryRequest.Container
property has some interesting possibilities. You can group related factories together into logically-related assemblies, and you can think of each one of these assemblies as a "layer" of factories in your application. For example, let's suppose that I created a very simple "connection" layer that relies on the connection strings provided by the ConfigurationStringFactory
class. Here's what the SqlConnectionFactory
class would look like:
[Factory(typeof(IDbConnection))]
public class SqlConnectionFactory : IFactory
{
public object CreateInstance(IFactoryRequest request)
{
var container = request.Container;
var connectionString = container.GetService<string>("Sql2k5ConnectionString");
var connection = new SqlConnection(connectionString);
return connection;
}
}
As you can see in the example above, there was no actual reference to the ConfigurationStringFactory
itself, and yet, the ConnectionFactory
was still able to access the services provided by the ConfigurationStringFactory
class. The ConnectionFactory
is only aware of the container's existence, and yet, the IFactoryRequest.Container
property allows every other factory to access all the other factories as if they have all been aggregated into the container at once. In other words, you can "drop in" (or replace) these assemblies at runtime without having to worry about making any breaking changes in your app. For example, here's the code to load the two assemblies named ConfigurationStringFactory.dll, and ConnectionFactory.dll:
container.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "*Factory.dll");
Aside from the "*Factory.dll" wildcard pattern that tells LinFu to load the factory assemblies into the container, the example above should be immediately familiar--it's the same example that I used to load the container in the beginning of this article. Using that single line of code, you can effectively use the attributes I mentioned in this article to bend LinFu to your will. LinFu.IOC has always been about giving you the flexibility to extend your applications without forcing you to follow any specific paradigm or coding convention (aside from DI/IOC, perhaps), and hopefully, this article will get you started in the right direction. Enjoy!
Points of Interest
Generics Support for the Generically Inclined
One thing that I mentioned in the beginning of this article is LinFu.IOC's support for instantiating open generic service types. As it turns out, you can actually instantiate a whole family of types that derive from (or implement) a generic type definition using LinFu. Take a look at this example:
[Implements(typeof(IList<>))]
public class MyCustomList<T> : List<T>
{
}
The ImplementsAttribute
declaration will tell LinFu.IOC to route all requests for any IList<T>
instance back to a MyCustomList<T>
instance, regardless of the type parameter being used to instantiate the list type. In contrast, if you want to handle only a specific type of IList<T>
type, you could declare the same class as follows:
[Implements(typeof(IList<string>))]
[Implements(typeof(IList<int>))]
public class MyCustomList<T> : List<T>
{
}
Either way, the choice of how you want to design your application is up to you, and the power of choice is ultimately what LinFu.IOC offers.
Coming Up in the Next Article
In Part II of this series, I'll show you how can use LinFu.IOC's built-in dependency injection features to automatically inject your dependencies into constructors, methods, properties, and even fields. I'll even show you how you can combine constructor injection with LinFu.IOC's additional parameters so that you can actually "fill in" the missing constructor parameters when instantiating a concrete service type. Here's an example:
public interface IWeapon {}
public interface IWarrior
{
void Attack(string target);
string Name { get; set; }
}
[Implements(typeof(IWarrior), ServiceName="Samurai")]
public class SamuraiWarrior : IWarrior
{
private IWeapon _weapon;
public SamuraiWarrior(IWeapon weapon, string warriorName)
{
_weapon = Weapon;
Name = warriorName;
}
public void Attack(string target)
{
Console.WriteLine("{0}: Attacking '{1}' with weapon '{2}",
Name, target, _weapon.GetType().Name);
}
public string Name { get; set; }
}
[Implements(typeof(IWeapon))]
public class SamuraiSword : IWeapon
{
}
As you can see, most of the code above is self-explanatory. What we need to do is somehow instantiate the Samurai
class every time a IWarrior
service named "Samurai" is requested from the container, and the container has to automatically inject the IWeapon
instance into the constructor so that it can instantiate the Samurai
type. In addition to the dependency injection, we also need to give the warrior a name. Here's the client code that does it:
var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var name = "Musashi";
var warrior = container.GetService<IWarrior>("Samurai", name);
warrior.Attack("Ninja");
What makes this example particularly interesting is that LinFu.IOC was smart enough to automatically inject both the IWeapon
dependency and the warrior name
value into the Samurai
class constructor without any form of intervention on your part. This makes it easy to do automatic dependency injection (as well as mixed constructor injection) in your client code because LinFu handles all the low-level details of deciding which constructors to use as well as which parameter values should be used in invoking your constructors. LinFu.IOC also supports automatic method, property, and field injection, but I'll save that example for the next article--so stay tuned, because this will definitely be worth the wait!
Special Thanks
A special thanks goes out to my colleague Bernhard Richter who really helped me put LinFu.IOC v2.0 through its paces. LinFu never would have been as robust as it is today without his support!
History
- 12.11.2008 - Article posted.