1

In response to my question here that was half way about having a circular dependency:

C# ASP.NET Dependency Injection with IoC Container Complications

The presenter depends on an IView. And the Page (implementing the IView) depends on a presenter. Other have solved this converting from constructor injection to property injection

I didnt like this because I felt that you now need to make public properties that can be modified externally, and also becomes the developers responsibilityies to initialise.

But it seems that I have been able to solve this another way.

By the page having a default constructor as well as an overloaded parameterised constructor, you can via reflection call the parameterless constructor to create the object then call the overloaded one by injecting the dependencies.

I'ill illustrate by example:

class TestObject
{
    public string Name { get; set; }

    public TestObject()
    {
       Name = "Constructed with no args";
       Console.WriteLine("constructor hash code: " + GetHashCode());
    }

    public TestObject(string name)
    {
        this.Name = name;
        Console.WriteLine("constructor hash code: " + GetHashCode());
    }
}

This object can be constructed simply:

var obj = Activator.CreateInstance<TestObject>();

or

var obj = new TestObject();

But then I can via reflection use the overloaded constructor to inject the dependencies:

ConstructorInfo ctor = obj.GetType().GetConstructors()[1];
ctor.Invoke(obj, new[] { "injected" });

I was able to use this approach to wire up structuremap to register the instance after creation and then inject the dependencies.

Of course we can use a regular method to inject the dependencies, but this again breaks the encapsulation a bit, so you could call this again to override dependencies.

Also as constructor this cant simply be reached by static code.

But I have no idea how I feel about this, it feels a little bit like a hack or something that i can simply do accidentally in C#.

I would like to hear your thoughts

Thanks.

Community
  • 1
  • 1
Andre
  • 3,717
  • 4
  • 25
  • 29
  • It's interesting that this is possible, but I think it's asking for long-term maintenance headaches. Imagine the poor maintenance engineer who's debugging your code, sees that the constructor is called twice and assumes that two instances must have been created somehow, somewhere. – Dan Bryant Dec 02 '11 at 14:59
  • Related: http://stackoverflow.com/questions/2053044/can-dependency-injection-prevent-a-circular-dependency – Mark Seemann Dec 02 '11 at 15:58

1 Answers1

1

I don't like this use of reflection as it means you can't easily create a view/presenter with out a an IoC container.

One way of solving the need for an IoC container is to use a factory to easily create a view/presenter and placing the reflection logic in the factory.

However, at this point you could have a simple property or Initialize(view)/Initialize(presenter) method. The responsibility for calling these methods is taken away from the developers by the factory or the IoC container (which is acting as a factory).

An alternative to passing the dependency to the object when constructing is to pass an factory that can build the dependency to the object. The simplest form of factory is a simple Func.

void Main()
{
    var controller = Controller.Create (c => new View (c));  
}

class Controller {
    private View view;

    // This could also be a constructor, but I prefer to think of this
    // as a factory method.
    public static Controller Create (Func<Controller, View> viewBuilder) {
        var controller = new Controller ();
        var view = viewBuilder (controller);
        controller.Initialize (view);
        return controller;
    }

    protected Controller() {
    }

    protected void Initialize (View view) {
        this.view = view;
    }
}

class View {
    private Controller controller;

    public View (Controller controller) {
        this.controller = controller;
    }
}

If you do insist upon having a pair of constructors I would make the no argument constructor protected unless it really is valid to create the view/presenter without a presenter/view. This would prevent someone using that constructor by mistake, since there's no way without reflection to initialize the view/presenter after construction.

Though I then think this is a slightly better on the reflection idea would be to make a parameterless constructor that is protected, and a protected initialize method.

public class TestObject
{
    protected TestObject () {
    }

    public TestObject (string name) {
        Initialize (name)
    }

    protected Initialize (string name) {}
}

This enforces that you can't construct it without a name, unless you use reflection. The reflection would be encapsulated in a factory which would ensure Initialize is also called.

Chris Chilvers
  • 6,429
  • 3
  • 32
  • 53
  • Though I agree with a lot that you are saying there are one or 2 problems. First the goal here is to DI ASP.NET. You cant make the parameterless constructor protected, the page is created by ASP and you have no control over this, all you can do is to in the handler factory inject dependencies into the already created object. The factory is a good solution, in fact it was one of the solutions, but I did point out in my post that this requires the developer to create a factory for each of his presenters, perhaps you can do this completely via generics, but im still trying to work this out – Andre Dec 02 '11 at 15:38
  • Chris Chilvers idea is useful, especially when that `Initialize` method is made public. When your `System.Web.Page` classes can have a public `Initialize` method by convention, you can look for that method using reflection and inject the dependencies it needs into it. While this still needs reflection, this will work in partial trust. Calling an overloaded constructor on an already initialized object will ***not*** work in partial trust. Still, mistype `Inatilize` and nothing gets injected. – Steven Dec 02 '11 at 15:53
  • Chris sorry if it seemed i doubted your answer, Steven thanks for making me realize this, yes this does work and what made me realize is that when you treat the initialize as a convention thereby enabling a factory to know what to invoke – Andre Dec 02 '11 at 15:59
  • Thanks guys, this has been close as it seems not to be constructive, I found your responses useful and I will go with this solution – Andre Dec 02 '11 at 16:14