Fernandos Configuration and Dependecny Injection Post
Fernando’s server is giving him problem so I am posting it here for everyone to enjoy .
Enterprise Library, System.Configuration and Dependency Injection
What we did
For the new version of EntLib, we did two major changes in the way to deal with configuration and in the way the blocks relate to configuration, as Scott announced a few days ago.
On the configuration side, we switched to the configuration management support provided by .NET Framework 2.0. This has a lot of implications that will be described in later posts, but the most important change is that now all configuration objects extend types from the System.Configuration namespace and its serialization behavior is no longer defined by Xml serialization attributes but by ConfigurationElement attributes. There’s an almost one to one mapping between the configuration support provided by the previous version of EntLib and new the configuration features in the .NET Framework, which makes the transition easier.
As for the relation between the feature objects and configuration, well, there were some major changes. A usual requirement for the previous version of EntLib was to make its features available without having to deal with configuration. One of the main goals for this new version was to make it possible to just create the feature objects, such as providers or databases, and use them without having to rely on any configuration whatsoever. Once we got that we were able to do very interesting stuff with Dependency Injection, as we will see later. For an introduction to Dependency Injection (DI) you can check out http://www.martinfowler.com/articles/injection.html.
Back to basics
We went back to the basics when analyzing how to simplify even further the usage patterns for EntLib.
To use a feature object, we need to get a reference to it. Usually, this means creating it or getting an existing reference to a previously created object.
So, what do we need to create an object? We just need to call the adequate constructor with the adequate parameters. Any user of the library should be able to do that.
What if we could have factories do the same? Just new up objects calling the constructors with the required parameters. And better yet, what if we could do it without writing specific factory code for each object? Well, that's what DI is all about.
Synergy between System.Configuration and DI
Our solution, which will be available on the first preview of this new version, is to make our Configuration objects collaborate with the factories in a generic way, so that the factories can get the appropriate parameters and call the right constructor on the feature objects without us having to write custom code for that.
The key for our solution to work is to annotate the configuration elements with what we call "InjectionParameter" attributes. These attributes let the factories know which constructor to use and how to acquire the parameters to call that constructor. This way, we only need a small injection engine that can interpret the attributes and fire the appropriate constructor lookup and parameter retrieval.
Even better, the configuration objects are not constrained to be ConfigurationElements in the System.Configuration sense for the factories to work well with them. The only requirement is that they have the adequate attributes for the mechanism to work. And of course, the use of these mechanisms is not limited to EntLib ;)
One very important design guideline we imposed ourselves was that feature objects should be completely unaware of configurations and factories. They should just do their job, as long as they were adequately constructed.
The first concept we define is the abstraction of "Factories", which are an evolution of what we already had in the previous version of EntLib. These abstractions are not coupled with the injection mechanism; they just define an API to create objects, whether using DI to do so or not. They are, however, used by the DI mechanism to get objects when required, as we'll see later.
There are some different ways to ask a factory for an object:
- To ask for the instance with a given name
- To ask for the default instance
- To give a configuration describing the instance we want the factory to build
A concept that surfaces here is that of "configuration source". It's very similar to the previous version’s ConfigurationContext, but the implementation differs. This will be explained in a later post.
Injection attributes & configuration decoration
Injection attributes are the way to indicate, in a configuration object, what constructor to use and how to get the parameters to call that constructor when creating the feature object described by the configuration object. There are several predefined injection attributes covering all the cases we came across, but the concept is extensible to suit whatever needs surface in the future. Again the concept is simple: provide an argument to a constructor. These are class attributes, used to annotate the configuration types.
This is how it looks:
[InjectionAttributeA(0, typeof(SomeType1), [extra argument parameters])]
[InjectionAttributeB(1, typeof(SomeType2), [extra argument parameters])]
[InjectionAttributeC(2, typeof(SomeType3), [extra argument parameters])]
public class MyObjectConfiguration
The first attribute parameter is the position of the constructor argument defined by it. Of course, these have to be consecutive and non-repeating, zero starting ints. The second attribute parameter is the declared type of the constructor argument. The rest of the parameters depend on the actual injection attribute.
This specification will match to a constructor like:
public class MyObject()
public MyObject(SomeType1 arg1, SomeType2 arg2, SomeType3 arg3)
(There’s no need for names to match in any way, it's just a naming convention).
Usually the arguments for the constructor will depend on the value of a property on the configuration object; however, some times they don't. That's the reason for the first classification for injection attributes: the ones that are related to properties in the configuration object and the ones that are not.
Since the property related injection attributes are by far the most common, we'll focus on them. All of them share an attribute parameter that specifies the name of the property they should deal with.
There are several interesting ways to deal with the properties of the configuration objects, and each has an injection attribute that represents that usage:
- Value: The simplest usage of a configuration property is to use its value as the constructor argument. This is usually the case for primitive types. The injection attribute used to specify this usage doesn't need further configuration.
- Nested configuration: in other cases the configuration object's aggregates a configuration object for another feature object type. In this case, we need a factory that can interpret this configuration. Not surprisingly, the injection attribute that represents this case needs a parameter to specify the factory type to use when getting the constructor argument. This is usually used with collections of configuration elements.
- Reference: It's very common for blocks to have named references to other blocks' feature objects; a usual case is a block object that uses a database. In this case the configuration object will have a string property representing the name of the referenced object. But we don't want the feature object we're configuring to get the name of the referenced object it needs, we want it to get the actual object! So we specify a reference usage that will take the value of the property and, assuming its value is a name, it will ask a suitable factory for the object with that name. So again we have to specify the factory we want to use to get this constructor argument.
The interesting implementations of the factory interfaces are the ones that actually use the injection attributes to do the work for us. In fact, these implementations are built using .NET 2.0 generics, so this gives us a very convenient mix of abstraction and type safety.
The basic implementation of these generic injection factories is:
- Get the configuration for the object to build. Depending on the overload called, this means:
- Just use the configuration passed as an argument
- Get the configuration from the configuration source based on a name
- Do something else: get it from an unnamed configuration location, create a configuration based on pieces lying around. The possibilities are unbounded
- Extract the injection configuration from the configuration object, based on the injection attributes from the configuration object type.
- Get the type of the object to build. This might be known in advance in some cases, but for factories of polymorphic hierarchies there's usually some piece of information in the configuration object to hint the type of feature object to create.
- Get the constructor of the object to build. This is just a matter of retrieving the constructor that matches the parameter types specified by the injection attributes.
- Get the constructor parameters to build the object. This involves both the injection configuration and the actual configuration object. For each parameter injection specification, the factory will get the corresponding constructor parameter in the way specified. Actually, lazy as it is, the factory will gently ask the injection specification to get the parameter on its behalf; this makes it possible to plug new parameter acquiring strategies very easily.
- Invoke the constructor with the arguments, and return it.
This is the default implementation, and will usually be enough for most of the factories. Concrete factories that support named object access must override the configuration lookup method; this usually involves just getting some configuration object from the configuration source, but it might be more involved in some cases.
After all this, how do we get to the actual objects? We have several options.
We can ask the factory for the object with a given name:
MyObject object = myObjectFactory.Create(“myObjectName”);
We can ask the factory to create the object with a given configuration:
MyObjectData data = new MyObjectData();
data.PropertyA = XXX;
data.PropertyB = YYY;
MyObject object = myObjectFactory.Create(data);
Or we can just new up the object the old fashioned way:
MyObject object = new MyObject(XXX, YYY, ...);
Once we have the object, we can make it work for us just like we did before.
This was just a glimpse of what will be available in the EntLib CTP. Later posts from the EntLib dev team will address:
· In depth explanations of the DI support: how it works, what will be available out of the box and what can be extended
· The way System.Configuration is leveraged, what was extended and what types were created to make things even easier
· End to end samples to show these mechanisms, both with blocks’ code samples and with completely new implementations.