Introduction
This article (and the associated code) demonstrates how to extend the Dependency Injection framework provided as part of the Enterprise Library (specifically, my team is using the January 2006 version).
Background
I'm leading a team of developers who are developing a new application based on the Smart Client Software Factory (SCSF). Using SCSF allows us to loosely couple the various components of the application and distribute their development to different developers.
One of the common tasks (at least in our application! :) ) is to display a list of items in a Management dialog, and then edit a selected item in a New/Edit dialog. The pattern create and displaying the view looks similar to this:
WorkItem.Items.Add(View.SelectedItem, "NewEditCostCentre");
INewAndEditCostCentre view = WorkItem.SmartParts.AddNew<NewAndEditCostCentre>();
WorkItem.Workspaces["WorkspaceName"].Show(view);
With the constructor for the views' presenter looking like this:
[InjectionConstructor]
public NewAndEditCostCentre([Dependency(Name = "NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]CostCentre entity)
The problem with the constructor code is that it actually needs to know the concrete type rather than the interface of the business entity, and this goes somewhat against the grain of CAB's "loosely coupled" ethos. What I'd really like to see in the constructor is this:
[InjectionConstructor]
public NewAndEditCostCentre([Dependency(Name = "NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]ICostCentre entity)
The problem with this is that it simply doesn't work. With the above setup, you will get a DependencyMissingException
because the DependencyResolver
is unable to find an ICostCentre
. Delving into the DependencyResolver
, the reason for this is that it takes the hash code for the ID and the type of the object you want injecting, OR
s them, and then uses this hash to efficiently search the collections for the object. The problem is that when the object is originally put into the collection, the hash used is based on the type implementing the interface.
Solution 1: Generate more DependencyResolutionLocatorKeys
This was the first solution I came up with. After rummaging through the code, I thought that using a bit of Reflection to get the implemented interfaces and the inherited types and then generating and storing DependencyResolutionLocatorKey
s for them all would resolve the problem. And, it probably would. However, with this solution, we're beginning to invoke the law of unintended consequences. The main one is that all of a sudden, we're generating a lot of hash keys, and I have no idea how good the original hash keys are, potentially breaking dependency injection in all sorts of interesting ways. (One of the reasons we're using such relatively old tech is that it still puts our customer ahead of their competition, but keeps them in the comfort zone of "Stuff We Know WorksTM".)
For those interested though, there appears to be four places where the change would need to be made:
- In the ObjectBuilder itself, in
CreationStrategy.RegisterObject
. - Three places within CAB:
WorkItem.InitializeFields
WorkItem.LocateWorkItem
ServiceCollection.BuildFirstTimeItem
Having looked and thought about it, I was sufficiently put off to have another think.
Solution 2: Modify DependencyResolver to be a "Bit More IntelligentTM"
Through a bit of clever lateral thinking, someone in Microsoft P&P decided to make the attributes used in dependency injection responsible for resolving their own dependencies. What this meant in practice for me was that DependecyAttribute
was creating the DependencyParameter
that was being put into a great big list of things to do within CAB. At the right point in the strategy, the processing retrieves the DependencyParameter
and asks for the value to be injected. DependencyParameter
uses DependencyResolver
to do this.
The new simple answer then was to put a bit of code into the resolver so that if the hash based lookup failed, it would go through the collection looking for items with the right ID, and then see if they could be cast to the wanted type. And, I even implemented this, and it worked.
But again, I was invoking that law. Another thing that the customer had asked us to do is make sure we share as much code across the products we maintain for them as is sensible. Obviously, EL and CAB are absolutely ripe for that. However, putting my code in the DependencyResolver
changes its behaviour, which someone else on one of the other projects may (inadvertently) be relying on. So, that solution joined number one in learning how to skydive (metaphorically).
Interlude
By this point, I'd spent far longer than was healthy looking at the problem, and it had long gone pub o'clock on Friday evening. I filed the problem away (mentally), and then proceeded to obliterate random parts of my brain. It was during this process that inspiration struck!
Solution 3: Create a new dependency injection attribute
I realised that someone in Microsoft P&P did a clever bit of lateral thinking. They made the attributes used in dependency injection responsible for resolving their own dependencies. At this point, I think it's only fair to point out that solution number two came about by several hours of careful and tedious debugging of what was actually happening in the code. Because, looking at the definition of DependancyAttribute
et al I couldn't for the life of me work out how CAB was making the link between the classes... Beer! Bringer of insight!
And the rest is almost history. The astute (and impatient) will have noticed that the three classes in ObjectBuilder.Extension are almost exactly the same as the three classes in ObjectBuilder. I did try using inheritance, etc., to implement the classes, but it wasn't working, so I went back to the basics and copy-pasted and edited the code.
Calling the code requires a minor change in order to work:
[InjectionConstructor]
public NewAndEditCostCentre([IADependency("NewEditCostCentre",
NotPresentBehavior = NotPresentBehavior.Throw,
SearchMode = SearchMode.Up)]ICostCentre entity)
The new attribute is IADependencyAttribute
, and this has a mandatory parameter of the ID of the item to be injected. Beyond this, thanks to some people far clever than me, ObjectBuilder can make use of the attribute and its supporting classes without knowing anything about it.
N.B.: The one thing that you need to be aware of is that the project has a reference to the generic Microsoft.Practices.ObjectBuilder.dll that is (usually) built when Enterprise Library is installed. This may need to be altered to be a reference to the actual DLL you are using, especially if you have signed it.
Points of interest
The IADependencyResolver
class is by far the most interesting of the three. This being where the work of getting the dependant value is actually done. This is the body of the ResolveBasedOnId
method:
IReadableLocator locator = context.Locator;
object retVal = null;
while ((retVal == null) && (locator != null))
{
foreach (KeyValuePair<object, object> pair in locator)
{
if (pair.Key is DependencyResolutionLocatorKey)
{
DependencyResolutionLocatorKey depKey =
(DependencyResolutionLocatorKey)pair.Key;
if (object.Equals(depKey.ID, id))
retVal = IsResolvable(pair.Value, typeToResolve);
}
if (retVal != null) return retVal;
}
if (searchMode == SearchMode.Up)
locator = locator.ParentLocator;
else
locator = null;
}
return retVal;
The IsResolvable
method simply uses Type
's IsAssignableFrom
method to see if the cast can be done. I'm 99.9% certain though that this could be done far better.
History
- 17th July, 2008 - Initial version. (Apologies for the length.)