views:

246

answers:

3

I'm running into an issue with Autofac2 and MVC2. The problem is that I am trying to resolve a series of dependencies where the root dependency is HttpRequestScoped. When I try to resolve my UnitOfWork (which is Disposable), Autofac fails because the internal disposer is trying to add the UnitOfWork object to an internal disposal list which is null. Maybe I'm registering my dependencies with the wrong lifetimes, but I've tried many different combinations with no luck. The only requirement I have is that MyDataContext lasts for the entire HttpRequest.

I've posted a demo version of the code for download here.

Autofac modules are set up in web.config

Global.asax.cs

protected void Application_Start()
{
    string connectionString = "something";

    var builder = new ContainerBuilder();

    builder.Register(c => new MyDataContext(connectionString)).As<IDatabase>().HttpRequestScoped();
    builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerDependency();
    builder.RegisterType<MyService>().As<IMyService>().InstancePerDependency();

    builder.RegisterControllers(Assembly.GetExecutingAssembly());

    _containerProvider = new ContainerProvider(builder.Build());
    IoCHelper.InitializeWith(new AutofacDependencyResolver(_containerProvider.RequestLifetime));

    ControllerBuilder.Current.SetControllerFactory(new AutofacControllerFactory(ContainerProvider));

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

AutofacDependencyResolver.cs

public class AutofacDependencyResolver
{
    private readonly ILifetimeScope _scope;

    public AutofacDependencyResolver(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public T Resolve<T>()
    {
        return _scope.Resolve<T>();
    }
}

IoCHelper.cs

public static class IoCHelper
{
    private static AutofacDependencyResolver _resolver;

    public static void InitializeWith(AutofacDependencyResolver resolver)
    {
        _resolver = resolver;
    }

    public static T Resolve<T>()
    {
        return _resolver.Resolve<T>();
    }
}

UnitOfWork.cs

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly IDatabase _database;

    public UnitOfWork(IDatabase database)
    {
        _database = database;
    }

    public static IUnitOfWork Begin()
    {
        return IoCHelper.Resolve<IUnitOfWork>();
    }

    public void Commit()
    {
        System.Diagnostics.Debug.WriteLine("Commiting");
        _database.SubmitChanges();
    }

    public void Dispose()
    {
        System.Diagnostics.Debug.WriteLine("Disposing");
    } 
}

MyDataContext.cs

public interface IDatabase
{
    void SubmitChanges();
}

public class MyDataContext : IDatabase
{
    private readonly string _connectionString;

    public MyDataContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    public void SubmitChanges()
    {
        System.Diagnostics.Debug.WriteLine("Submiting Changes");
    }
}

MyService.cs

public interface IMyService
{
    void Add();
}

public class MyService : IMyService
{
    private readonly IDatabase _database;

    public MyService(IDatabase database)
    {
        _database = database;
    }

   public void Add()
   {
       // Use _database.
   }
}

HomeController.cs

public class HomeController : Controller
{
    private readonly IMyService _myService;

    public HomeController(IMyService myService)
    {
        _myService = myService;
    }

    public ActionResult Index()
    {
        // NullReferenceException is thrown when trying to
        // resolve UnitOfWork here.
        // Doesn't always happen on the first attempt.
        using(var unitOfWork = UnitOfWork.Begin())
        {
            _myService.Add();

            unitOfWork.Commit();
        }

        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}
+1  A: 

In the HomeController you are disposing the UnitOfWork which autofac will do for you. However if you wish to control when the object is disposed you will need to add ExternallyOwned to you registration for IUnitOfWork.

builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerDependency().ExternallyOwned();
Thad
That was it! Thanks!
Page Brooks
+2  A: 

You need to initialise your AutofacDependencyResolver with the ContainerProvider, not the RequestLifetime (which only lives as long as the current request - a new one is created every time.)

Hope his helps,

Nick

Nicholas Blumhardt
Thanks for your help Nick. For my Resolve method, should I still use RequestLifeTime.Resolve inside the AutofacDependencyResolver or should I use ApplicationContainer.Resolve? I'm thinking it would still be RequestLifeTime.Resolve.
Page Brooks
Yes, RequestLifetime is the one.
Nicholas Blumhardt
+2  A: 

First off, you should not let UnitOfWork be dependent on the container. Remove the BeginWork method and consider this change to the HomeController:

public class HomeController : Controller
{
    private readonly IMyService _myService;
    private readonly Func<Owned<IUnitOfWork>> _unitOfWorkFactory;

    public HomeController(IMyService myService, Func<Owned<IUnitOfWork>> unitOfWorkFactory)
    {
        _myService = myService;
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public ActionResult Index()
    {
        using(var unitOfWork = _unitOfWorkFactory())
        {
            _myService.Add();

            unitOfWork.Value.Commit();
        }

        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}

The Func<> factory delegates are automatically available in Autofac2 and will give you a delegate that creates instances of the specified type. Next, since we ask for Owned<IUnitOfWork> we get an instance that

Under the hood, an Owned is allocated its own nested lifetime scope, so all of its dependencies will be cleaned up when it is.

The Owned instance indicates that you as the dependency consumer is responsible for disposing the instance.

Owned is (imo: genius!) preferred above using ExternallyOwned since disposing an externally owned instance will not clean up other dependencies injected into that instance. Disposing an Owned instance will also automatically dispose the whole object graph for that instance.

Some introduction to this can be found here.

Note: even better, now that UnitOfWork is free from the container, you can throw out the IoCHelper thing altogether :)

Peter Lillevold
Thanks for your detailed answer Peter! This is awesome! I really didn't want to use the IoCHelper (Service Locator).
Page Brooks
Awesome addition, good thinking! :)
Nicholas Blumhardt