Dependency injection for Visual Studio extensions

Akos Nagy
Feb 14, 2019

Dependency injection is awesome. I have written a lot of posts that start the same way; mostly because dependency injection is awesome. I'm not going into the details of why (I have already done that in those posts), let's just assume I'm right :)

When using dependency injection, there is a component in the system that's called the composition root. This is basically the place where the whole object graph needed to execute a request is built up. For a traditional ASP.NET application this is the ControllerFactory, for a WCF app it is the ServiceHostFactory. This is also the place where the required objects are resolved from the DI container. My choice of DI container is Autofac, and it contains integration components to support implementing the composition root for most frameworks, like ASP.NET or WCF.

I've been working on a Visual Studio extension that you'll hopefully all get to download soon :) The extension is basically a WPF app that is hosted inside Visual Studio as a command window. I've built the application first as a regular WPF app, using MVVM and dependency injection like you should and then turned the regular app to a Visual Studio extension. The problem is that Visual Studio SDK is not the most popular framework out there, so it's not just there is no Autofac integration for this scenario, but there aren't even guidelines on how and where to implement the composition root. So I had to come up with a way myself. After looking through decompiled assemblies and some trial and error, I've come up with a way that works and seems solid. But use this info only at your own risk :); I'm not a 100% on this, since I haven't found any supporting documentation.

Finding the composition root and integrating Autofac to a Visual Studio extension

When you create a VSIX project and add a Custom tool window, a new package class is added to the project that descends AsyncPackage. This class implements the interface that must be used in every Visual Studio extension and also provides some base functionality to make developing extensions a little easier (altough it's still complicated as hell). This class has methods to create the tool window and to create objects to execute requests. This is where the composition root can be implemented. So I created a class that extends this and has Autofac integrated:

public class AutofacEnabledAsyncPackage : AsyncPackage
{
  private IContainer container;
  private readonly ContainerBuilder containerBuilder;
  public AutofacEnabledAsyncPackage()
  {
    containerBuilder = new ContainerBuilder();
  }

  public void RegisterModule<TModule>() where TModule : Autofac.Core.IModule, new()
  {
    containerBuilder.RegisterModule<TModule>();
  }

  protected override System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
  {
    container = containerBuilder.Build();
    return base.InitializeAsync(cancellationToken, progress);
  }

  protected override object GetService(Type serviceType)
  {
    if (container?.IsRegistered(serviceType) ?? false)
    {
      return container.Resolve(serviceType);
    }
    return base.GetService(serviceType);
  }

  protected override WindowPane InstantiateToolWindow(Type toolWindowType) => (WindowPane)GetService(toolWindowType);

  protected override void Dispose(bool disposing)
  {
    try
    {
      container?.Dispose();
    }
    catch { }
    finally
    {
      base.Dispose(disposing);
    }
  }
}

So basically this class has a Container and a ContainerBuilder. The builder is instantiated in the constructor. Then there is method that lets you register an Autofac module to the container. The InitializeAsync() method is extended to build the container. The Dispose() method is also extended to dispose the container. The real trick is to override the GetService() method that is used to instantiate components for the VSIX project — it is now changed to resolve components from the container (if the container is built and the service is registered). The other very important thing is to override the InstantiateToolWindow() that calls the GetService() (which in turn calls the resolve from the container again).

And your basically done. All you have to do now is to create an Autofac module, where you register your classes:

public class BusinessServicesModule : Module
{
  protected override void Load(ContainerBuilder builder)
  {
    base.Load(builder);
    builder.RegisterType<ToolWindow1>();
    builder.RegisterType<ToolWindow1Control>();
    builder.RegisterType<ToolWindowViewModel>().SingleInstance();
    builder.RegisterType<ToolWindowService>().As<IToolWindowService>();
  }
}

Change the class that extends the ToolWindowPane to accept the window control as a contructor parameter:

public ToolWindow1(ToolWindow1Control toolWindowControl) : base(null)
{
  this.Caption = "ToolWindow1";         
  this.Content = toolWindowControl;
}

And finally change your package to extend the new Autofac enabled package class, register your module and call the base InitializeAsync():

public sealed class ToolWindow1Package : AutofacEnabledAsyncPackage
{

  public const string PackageGuidString = "83b2c648-d2d6-4408-bfdf-18b6f891e2d5";
  public ToolWindow1Package()
  {
    RegisterModule<BusinessServicesModule>();
  }
               
  protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
  {
    await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
    await ToolWindow1Command.InitializeAsync(this);
    await base.InitializeAsync(cancellationToken, progress);
  }
}

And you're done! Now you can simply use dependency injection in your usercontrols, viewmodels and other services and just register them in your Autofac module. You can check out the full source code later, when the extension is published!

Akos Nagy