Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C#
Article

Extending ObjectBuilder: Making dependancy injection inheritance aware

Rate me:
Please Sign up or sign in to vote.
4.43/5 (3 votes)
17 Jul 2008CPOL5 min read 28.9K   110   28   1
An article on extending the dependency injection framework provided by the ObjectBuilder from the Microsoft Patterns and Practices group.

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:

C#
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:

C#
[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:

C#
[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, ORs 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 DependencyResolutionLocatorKeys 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:

C#
[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:

C#
IReadableLocator locator = context.Locator;
object retVal = null;

// This loop is for if we're going up the parents of the list
while ((retVal == null) && (locator != null))
{
    // Go through all the items in the locator
    foreach (KeyValuePair<object, object> pair in locator)
    {
        // We're only interested in things that
        // have a DependencyResolutionLocatorKey key
        if (pair.Key is DependencyResolutionLocatorKey)
        {
            DependencyResolutionLocatorKey depKey = 
                     (DependencyResolutionLocatorKey)pair.Key;

            // See if the key's id and the id we're looking for match
            if (object.Equals(depKey.ID, id))
                retVal = IsResolvable(pair.Value, typeToResolve);
        }

        // Do a check to see if we've found
        // our object (no point carrying on if we have!)
        if (retVal != null) return retVal;
    }

    // See if we need to go up to the parent.
    // This is done under the following conditions:
    //      - searchMode has been set to up
    if (searchMode == SearchMode.Up)
        locator = locator.ParentLocator;
    else
        locator = null;
}

// Return whatever we have (or haven't) found
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.)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralVery nice Pin
Sergey Morenko17-Jul-08 5:09
professionalSergey Morenko17-Jul-08 5:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.