MEF, IServiceProvider and Testing Visual Studio Extensions

MEF, IServiceProvider and Testing Visual Studio Extensions

June 8, 2010 11:42 pm

In the latest and greatest version of Visual Studio, MEF plays a critical role, one that makes extending VS much more fun than it ever was.

So typically, you just [Export] something, and then someone [Import]s it and that’s it. MEF in all its glory kicks in and gets all your dependencies satisfied.

Cool, you say, so let’s now import ITextTemplating and have some T4-based codegen going! Ah, if only it was that easy. Turns out by default, none of the VS built-in services are exposed to MEF, apparently because there wasn’t enough time to analyze the lifetime, initialization, dependencies, etc. for each one before launch, which makes perfect sense. You don’t want to blindly export everything now just in case. There’s also the whole VS package initialization thing which in this version of VS is not so transparently integrated with the MEF publishing side (i.e. a MEF export from a package can get instantiated before its owning package, and in fact, the package can remain unloaded forever and the export will continue to be visible to anyone).

So, you just have to calm down, and re-encounter “good”-old IServiceProvider. Turns out, that’s still the “blessed” way to get your dependencies, but now you can get the service provider from MEF instead:

[ImportingConstructor]
public TextTemplate([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)

Note that in typical VS fashion, the actual exported contract (interface) is SVsServiceProvider, which can be converted automatically to an IServiceProvider.

But, if you make your entire class just work off of this service locator (doing GetService here and there), you lose all the “explicit-ness” that comes from having a proper constructor that declares that this class needs, say, an ITextTemplating. This makes it much harder for consumers attempting to reuse the implementation to figure out what this component needs.

Fortunately, MEF does not require your importing constructors to be public, so you can provide the MEF-exclusive constructor as internal, and make the constructor with your explicit dependencies public:

[ImportingConstructor]
private TextTemplate([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
    : this(serviceProvider.GetService<STextTemplating, ITextTemplating>())
{
}

public TextTemplate(ITextTemplating templating)
{
    Guard.NotNull(() => templating, templating);

    this.templating = templating;
}

Your tests will also instantiate this class exclusively from the non-MEF constructor, and pass moqs as needed. (note I’m using a simple generic GetService extension method on IServiceProvider to make code more concise).

/kzu

/kzu

/kzu dev↻d