I have an ASP.NET MVC project which uses Entity Framwork, SignalR and Hangfire jobs.
My main (root) container is defined this way:
builder.RegisterType<DbContext>().InstancePerLifetimeScope(); // EF Db Context
builder.RegisterType<ChatService>().As<IChatService>().SingleInstance(); // classic "service", has dependency on DbContext
builder.RegisterType<ChatHub>().ExternallyOwned(); // SignalR hub
builder.RegisterType<UpdateStatusesJob>().InstancePerDependency(); // Hangfire job
builder.RegisterType<HomeController>().InstancePerRequest(); // ASP.NET MVC controller
IContainer container = builder.Build();
For MVC I'm using Autofac.MVC5 nuget package. Dependency resolver:
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
For SignalR I'm using Autofac.SignalR nuget package. Dependency resolver:
GlobalHost.DependencyResolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);
My signalR hub is instantiated this way (http://autofac.readthedocs.org/en/latest/integration/signalr.html#managing-dependency-lifetimes):
private ILifetimeScope _hubScope;
protected IChatService ChatService;
public ChatHub(ILifetimeScope scope) {
_hubScope = scope.BeginLifetimeScope(); // scope
ChatService = _hubScope.Resolve<IChatService>(); // this service is used in hub methods
}
protected override void Dispose(bool disposing)
{
// Dipose the hub lifetime scope when the hub is disposed.
if (disposing && _hubScope != null)
{
_hubScope.Dispose();
}
base.Dispose(disposing);
}
For Hangfire I'm using Hangfire.Autofac package:
config.UseActivator(new AutofacJobActivator(container));
Jobs are instantiated this way:
private readonly ILifetimeScope _jobScope;
protected IChatService ChatService;
protected BaseJob(ILifetimeScope scope)
{
_jobScope = scope.BeginLifetimeScope();
ChatService = _jobScope.Resolve<IChatService>();
}
public void Dispose()
{
_jobScope.Dispose();
}
Question/problem: I always get same instance of DbContext in hubs and jobs. I want that all hub instances will get the same ChatService, but the DbContext (which is dependency of ChatService) will always be a new instance. Also Hangfire jobs should act the same.
Can this be done, or am I missing something?
Update 1:
After thinking (and sleeping over) I think I have two choices. I still want to to keep "session per request" ("session per hub", "session per job").
Option 1:
Change that all services will have InstancePerLifetimeScope. Instantiation of services is not expensive. For the services which maintains some kind of state I would create another "storage" (class) which will be SingleInstance and will not have dependency on session (DbContext). I think this will work for hubs and jobs also.
Option 2:
Create some kind of factory which was suggested by @Ric .Net. Something like this:
public class DbFactory: IDbFactory
{
public MyDbContext GetDb()
{
if (HttpContext.Current != null)
{
var db = HttpContext.Current.Items["db"] as MyDbContext;
if (db == null)
{
db = new MyDbContext();
HttpContext.Current.Items["db"] = db;
}
return db;
}
// What to do for jobs and hubs?
return new MyDbContext();
}
}
protected void Application_EndRequest(object sender, EventArgs e)
{
var db = HttpContext.Current.Items["db"] as MyDbContext;
if (db != null)
{
db.Dispose();
}
}
I think that this would work for MVC, but I don't know hot to get it working for hubs (every hub call is new instance of the hub) and jobs (every run of a job is a new instance of the job class).
I am leaning towards option 1. What do you think?
Many thanks!
I'm completely unexperienced with AutoFac. But what got my attention was:
I want that all hub instances will get the same ChatService, but the DbContext (which is dependency of ChatService) will always be a new instance.
What you're basically saying here is:
"My car is in maintenance by the same the car company which have a dependency on their garage, but everytime I bring my car I want the garage to be a new one".
When you inject the (completely build instance, including dependencies of) ChatService
in some other component, of course other dependencies it has will be build also, whether they have an other kind of lifestyle or not. When an object with a shorter lifetime than the object it is injected in is created, you've created a so called 'captive dependency'
The only way to get a new 'instance' of the DbContext
in your ChatService
is not to inject the DbContext
itself but by injecting a DbContextFactory
which creates the DbContext
for you whenever you use it.
An implementation would look something like:
public class DbContextFactory
{
public DbContext Create()
{
return new DbContext();
}
}
//usage:
public class ChatService
{
private readonly DbContextFactory dbContextFactory;
public ChatService(DbContextFactory dbContextFactory)
{
this.dbContextFactory = dbContextFactory;
}
public void SomeMethodInChatService()
{
using (var db = this.dbContextFactory.Create())
{
//do something with DbContext
}
}
}
The DbContextFactory
could be registered in AutoFac using the Singleton Lifestyle.
This maybe is however not what you aim for. Because in this case every time you use the DbContext
you get a new one. On the other hand, a new DbContext is probably the safest way to approach this, as you can read here.
This great answer is worth reading for more than one reason because it has an explanation of how to use the command / handler pattern which should be very suitable for your situation.
This would make your chatservice completely unknowing of the DbContext
which improves the 'SOLID' design of your application and creates the possibilty to test the ChatService
which is something that is practically undoable when injecting a DbContext
or DbContextFactory
directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With