4
votes

I have a base controller class that exposes some logging functionality to it's child classes. This logging dependency is constructor injected. To provide some simplified code this is how it all looks like:

public abstract class LogControllerBase : Controller
{
    private readonly ILogger logger = null;

    protected LogControllerBase(ILogger logger)
    {
        this.logger = logger;
    }
}

My child controllers also have their own dependencies hence they look something like this:

public class SomeController : LogControllerBase
{
    private readonly IService service = null;

    [ImportingConstructor]
    public SomeController(ILogger logger, IService service)
        : base(logger)
    {
        this.service = service;
    }
}

I'm using these constructors to make it easy to do dependency injection in my unit tests but in production all composition is (should be) done by MEF. I'm using my custom controller factory class that uses MEF to instantiate controllers.

To sum it up:

  1. I have a base abstract controller class that has its own dependencies
  2. I have descendant controllers that use importing to get their parameters injected by MEF (that is their dependencies as well as base class' ones)
  3. Unit tests don't use MEF so mocks are injected to constructor parameters

The problem

This all holds water to me, but MEF thinks differently. When I compile and run this code I get this exception:

GetExportedValue cannot be called before prerequisite import 'SomeController..ctor (Parameter="logger", ContractName="ILogger")' has been set.

All interface types that are used as constructor parameters have attribute InheritedExport set on them and also have concrete implementations, so it should work as expected.

Working alternative which isn't the same

When I try an alternative approach by doing imports directly on those private fields everything seems to work just fine.

public abstract class LogControllerBase : ControllerBase
{
    [Import]
    private ILogger logger = null;

    protected LogControllerBase() { }
}

public class SomeController : LogControllerBase
{
    [Import]
    private IService service = null;

    public SomeController() { }
}

So this works but it's not the same... I could add constructors here for dependency injection, but then I'd have two sets of constructors and when doing unit tests one may use the parameterless constructor which would of course be wrong because no dependencies would get set. Not real ones nor mocks.

Question

How can I convince MEF to create my controllers by injecting concrete implementations in constructor by setting dependency injection constructor as ImportingConstructor?

1
@Steven: Unfortunately that's not possible because MEF is internal policy set by a higher library that must be used in my web application. And to tell you the truth considering other requirements related to this app I'd choose MEF myself as well. There are other benefits to it that can't be easily done with other statically defined DI containers.Robert Koritnik
I really can't imagine that there is not much you cannot do with other containers. In fact, almost all other containers are much faster and much feature rich. The only thing I think MEF shines in, is handling plugin architectures, where the plugins run in different app domains (as VS does).Steven
@DarinDimitrov: No go, because A library I have to use uses MEF. So that's not really a solution. It may be a healthy suggestion but I can't take it. Thanks anyway Darin.Robert Koritnik
I understand this very well. That's why I didn't post it as answer. It was more as an incitation for you to talk to the people responsible for taking those design decisions in the company you are working for. Who knows, they might even listen to you :-)Darin Dimitrov
AFAIK MEF plugins run in the same AppDomain. If VS plugins run in a different AppDomain, then that's not a feature of MEF. I think MAF is the one that does the AppDomain separation.Jim Counts

1 Answers

0
votes

This might be related to threading, as MEF containers are not constructed as thread-safe by default. In your constructor for your container, try enabling thread-safety:

var container = new CompositionContainer(catalog, true);