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;
}
publi开发者_如何学运维c 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();
}
}
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 :)
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
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();
精彩评论