Transitioning to AutofacWebApiDependencyResolver from MVC's DependencyResolver - Where is .Current?

StackOverflow https://stackoverflow.com/questions/22331095

  •  13-06-2023
  •  | 
  •  

Question

I had AutoFac working properly with MVC4. I'm trying to transition to Web API 2. Here's what I've got for setting up AutoFac:

public class AutofacRegistrations
{
    public static void RegisterAndSetResolver()
    {
        // Create the container builder.
        var containerBuilder = new ContainerBuilder();

        // Register the Web API controllers.
        containerBuilder.RegisterApiControllers(Assembly.GetExecutingAssembly());

        //  Only generate one SessionFactory ever because it is expensive.
        containerBuilder.Register(x => new NHibernateConfiguration().Configure().BuildSessionFactory()).SingleInstance();

        //  Everything else wants an instance of Session per HTTP request, so indicate that:
        containerBuilder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).InstancePerApiRequest();
        containerBuilder.Register(x => LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType)).InstancePerApiRequest();

        containerBuilder.RegisterType<NHibernateDaoFactory>().As<IDaoFactory>().InstancePerApiRequest();
        containerBuilder.RegisterType<StreamusManagerFactory>().As<IManagerFactory>().InstancePerApiRequest();

        // Build the container.
        ILifetimeScope container = containerBuilder.Build();

        // Create the depenedency resolver.
        var dependencyResolver = new AutofacWebApiDependencyResolver(container);

        // Configure Web API with the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver = dependencyResolver;
    }
}

I'm pretty confident all of that is correct. My problem arises when I'm trying to setup some test cases. My base class for my test cases isn't a controller so it isn't automatically passed anything. In MVC4 I did the following:

[SetUp]
public void SetUp()
{
    HttpSimulator = new HttpSimulator().SimulateRequest();

    Logger = DependencyResolver.Current.GetService<ILog>();
    DaoFactory = DependencyResolver.Current.GetService<IDaoFactory>();
    Session = DependencyResolver.Current.GetService<ISession>();
    ManagerFactory = DependencyResolver.Current.GetService<IManagerFactory>();
}

[TearDown]
public void TearDown()
{
    HttpSimulator.Dispose();
}

Unfortunately, there's no DependencyResolver.Current in WebAPI. So I'm left wondering how to do this properly?

This builds, but is NOT correct. I received the message "Session Closed!" when I try to execute a test case:

[SetUp]
public void SetUp()
{
    using (var scope = GlobalConfiguration.Configuration.DependencyResolver.BeginScope())
    {
        //  TODO: Consider initializing Helpers during setup to keep this DRY.
        Logger = (ILog)scope.GetService(typeof(ILog));
        DaoFactory = (IDaoFactory)scope.GetService(typeof(IDaoFactory));
        Session = (ISession)scope.GetService(typeof(ISession));
        ManagerFactory = (IManagerFactory)scope.GetService(typeof(IManagerFactory));
    }
}
Was it helpful?

Solution

With WebAPI, you don't need access to the current resolver because the current dependency scope generally comes along with the inbound HttpRequestMessage. That message is also what's responsible for generating the new request scope.

You can see the code for this in the System.Net.Http.HttpRequestMessageExtensions.GetDependencyScope method.

One big thing you'll notice in WebAPI is that you don't actually need to set anything in global static values - that is, you don't need to set a global configuration/resolver because everything is instance-based now.

For testing, what this means is:

  • Your setup will create an HttpRequestMessage with the appropriate dependency resolver and configuration.
  • Your teardown will dispose of the HttpRequestMessage which will, in turn, dispose the dependency scopes, etc. down the line.
  • Individual tests will use HttpRequestMessage.GetDependencyScope() if they need to do manual resolution of something and the request message will be used to coordinate/pass around the scope.

In a more concrete fashion, it might look like:

private HttpRequestMessage _request;

[SetUp]
public void SetUp()
{
  var builder = new ContainerBuilder();
  // Register stuff.
  var container = builder.Build();
  var resolver = new AutofacWebApiDependencyResolver(container);

  var config = new HttpConfiguration();
  config.DependencyResolver = resolver;
  config.EnsureInitialized();

  this._request = new HttpRequestMessage();
  this._request.SetConfiguration(config);
}

[TearDown]
public void TearDown()
{
  this._request.Dispose();
}

[Test]
public void Test()
{
  // When you need to resolve something, use the request message
  this._request.GetDependencyScope().GetService(typeof(TheThing));
}

What's nice about this is that you don't have to fight with global configuration settings or resetting static values after every test.

You might wonder why you'd pass around the whole request message rather than just the dependency resolver - the reason is that the request message is what coordinates and controls the lifetime of the dependency scope. Otherwise, when you call GetDependencyScope multiple times, you'll get multiple different scopes rather than the same one as you'd expect.

Some things to consider from a design perspective:

  • You might want to put the actual registrations of things into an Autofac module so it can be reused in both tests and in your RegisterAndSetResolver method without having to worry about global statics getting tampered with.
  • Instead of a RegisterAndSetResolver modifying the global static configuration, you might consider just setting the resolver on the HttpConfiguration object that gets wired up in that WebApiConfig.Register method that WebAPI gives you. Take an HttpConfiguration object in as a parameter and set the resolver on that rather than the global.
  • If you're doing every registration for everything in a unit test, your unit tests might be closer to "integration tests." You might consider looking at only what's required for the stuff you're testing and using a mock framework to register stubs rather than actually registering a boatload of "real stuff."

Anyway, HttpRequestMessage is the way to go for WebAPI.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top