
Note: To access the latest sources for the LinFu v2.0 Framework directly from the SVN, click here.
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.
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.
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.
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.
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:
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. 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. IDisposable instance that it creates within a given using block or scope. 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. 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!
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:
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. In general, there are only a few things that you need to know to use the basic functions of LinFu.IOC. They are:
The ServiceContainer class is the heart of LinFu.IOC, and it's the only class that you have to create to get started:
// This is where it all begins and ends
var container = new ServiceContainer();
// Tell the container to configure itself
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.
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:
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:
// Strongly-typed GetService method call
var myService = container.GetService<ISomeService>();
// Weakly-typed GetService method call
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.
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:
// Get the connection string for an Oracle connection
var serviceName = "OracleConnectionString";
var connectionString = container.GetService<string>(serviceName);
// Get the connection string for SQL Server 2005
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.
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:
// This is equivalent to:
// IGreeter greeter = new Greeter(myName);
// NOTE: I'm using a null parameter for the service name parameter
// since I don't need a particular named service instance
var myName = "Me";
var greeter = container.GetService<IGreeter>(null, myName);
// Say "Hello, Me!"
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.
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();
// Strongly-typed registration
container.AddService<ISomeService>(someServiceInstance);
// Weakly-typed registration
container.AddService(typeof(ISomeService), someService);
If you need to use named services, here's how you can register those connection strings with the container:
// Register the Oracle connection string
var connectionString = "Driver={Oracle in OraHome92};
Server=myServerAddress;Dbq=myDataBase;Uid=myUsername;Pwd=myPassword;";
container.AddService("OracleConnectionString", connectionString);
// Register the SQL Server 2005 connection string
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.
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:
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);
}
}
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:
// Load the greeter library
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.
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.
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:
// Tell LinFu.IOC that the factory can create string types
[Factory(typeof(string))]
public class ConfigurationStringFactory : IFactory
{
public object CreateInstance(IFactoryRequest request)
{
// Use the name of the service as the
// key name in the config file
var keyName = request.ServiceName;
// Grab the key value
return ConfigurationManager.AppSettings[keyName];
}
}
Here's the client code that uses GetService to get a particular configuration string out of the config file:
// Get the "key1" configuration string.
// NOTE: Notice that the client doesn't even know where the config string comes from
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.
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)
{
// For the sake of brevity, argument type/count checking has been omitted
int a = (int)request.Arguments[0];
int b = (int)request.Arguments[1];
return a + b;
}
}
The client code would look something like this:
// Add the two numbers together
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.
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:
// Generate Sql2k5Connections by default
[Factory(typeof(IDbConnection))]
public class SqlConnectionFactory : IFactory
{
public object CreateInstance(IFactoryRequest request)
{
var container = request.Container;
// Use the connection string provided by the ConfigurationStringFactory
var connectionString = container.GetService<string>("Sql2k5ConnectionString");
var connection = new SqlConnection(connectionString);
// NOTE: You can probably cache the connection here, but for the
// sake of simplicity, we'll just return the connection
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:
// Load all the factories into the container
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!
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:
// Implement IList<> for string and integer types
[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.
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; }
}
// Inject the SamuraiSword by default
[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:
// Load the container
var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
// Create the Samurai
var name = "Musashi";
var warrior = container.GetService<IWarrior>("Samurai", name);
// NOTE: The output will be: "Musashi: Attacking 'Ninja' with SamuraiSword"
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!
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!
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||