asp.net-mvcautofaclightspeed

Why is autofac disposing an object before the HttpRequest ends?


I'm writing an ASP.NET MVC website, using autofac for dependency injection, and Mindscape's Lightspeed as the ORM. There's a UserRepository class, which depends on a lightspeed UnitOfWork, and which services the Logon controller.

Problem: The UnitOfWork gets disposed before the UserRepository is finished using it.

  public class UserRepository : IUserRepository
  {
    private readonly BluechipModelUnitOfWork _unitOfWork;

    public UserRepository(BluechipModelUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }
    public Principal GetPrincipal(string name)
    {
        // This line throws an ObjectDisposedException - UnitOfWork is already disposed.
        return _unitOfWork.Principals.FirstOrDefault(p => p.Name == name);
    }
    ...

In Global.asax, the dependency wiring is done as follows:

public class MvcApplication : HttpApplication, IContainerProviderAccessor
{
    private static void RegisterAutofac()
    {
        var builder = new ContainerBuilder();

        // Register the lightspeed context as a singleton
        builder.RegisterInstance(new LightSpeedContext<BluechipModelUnitOfWork>("LightSpeedBluechip"))
            .As<LightSpeedContext<BluechipModelUnitOfWork>>()
            .SingleInstance();

        // Register the unit of work constructor so that a new instance is bound to each HttpRequest
        builder.Register(c => c.Resolve<LightSpeedContext<BluechipModelUnitOfWork>>().CreateUnitOfWork())
            .As<BluechipModelUnitOfWork>()
            .InstancePerLifetimeScope();

        // Register user repository to be one instance per HttpRequest lifetime
        builder.Register(c => new UserRepository(c.Resolve<BluechipModelUnitOfWork>()))
            .As<IUserRepository>()
            .InstancePerLifetimeScope();

        builder.Register(c => new CurrentUserService(
                                  c.Resolve<HttpSessionState>(),
                                  c.Resolve<IUserRepository>(),
                                  c.Resolve<IMembershipService>())
            ).As<ICurrentUserService>()
            .CacheInSession();

        builder.RegisterType<ExtensibleActionInvoker>().As<IActionInvoker>();
        builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired().InjectActionInvoker();
        builder.RegisterModelBinders(Assembly.GetExecutingAssembly());

        // Set the container provider up with registrations.    
        _containerProvider = new ContainerProvider(builder.Build());

        // Set the controller factory using the container provider.    
        ControllerBuilder.Current.SetControllerFactory(new AutofacControllerFactory(_containerProvider));

Given the above registrations, why would autofac be disposing the UnitOfWork (


Solution

  • I was able to track down the problem - it's a dumb but subtle gotcha... I had a CurrentUserService class which I was registering as follows:

        builder.Register(c => new CurrentUserService(
                                      c.Resolve<HttpSessionState>(),
                                      c.Resolve<IUserRepository>(),
                                      c.Resolve<IMembershipService>())
                ).As<ICurrentUserService>()
                .CacheInSession();
    

    The problem is CacheInSession(), because the CurrentUserService depends on IUserRepository, which autofac was faithfully injecting, but then disposing of at the end of the first request.

    This brings into light something obvious, yet subtle to be aware of when wiring up dependency injections:

    Make sure that higher-order dependants always have the same or shorter lifetime as the services upon which they depend. In my case, the solution was to change the above code:

            builder.Register(c => new CurrentUserService(
                                      c.Resolve<HttpSessionState>(),
                                      c.Resolve<IUserRepository>(),
                                      c.Resolve<IMembershipService>())
                ).As<ICurrentUserService>()
                .InstancePerLifetimeScope();
    

    .... which prevents the CurrentUserService from out-living the instance upon which it depends.