5
votes

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!

2
You shouldn't be messing around with the scope. In fact, you don't need to pass it to your constructor at all. Your services should be passed via the DI container, and let autofac deal with your scope.ESG
You should also double-check the documentation. A component with per-lifetime scope will have at most a single instance per nested lifetime scope.ESG
@TheVedge: I agree with you. Suggested style (passing container to constructor) is from documentation: autofac.readthedocs.org/en/latest/integration/…rrejc

2 Answers

4
votes

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.

1
votes

You need to resolve a factory. Autofac has built-in support for Func<T> see Dynamic instantiation for example.

If your dependency has disposable dependency, you will have to manage the dispose pattern in order to avoid memory leak. A common pattern to resolve this using Autofac is to use Func<Owned<T>>

public class ChatService
{
    public ChatService(Func<Owned<DbContext>> dbContextFactory)
    {
        this._dbContextFactory = dbContextFactory;
    }

    private readonly Func<Owned<DbContext>> _dbContextFactory;

    private void DoSomething()
    {
        using (Owned<DbContext> ownedDbContext = this._dbContextFactory())
        {
            DbContext context = ownedDbContext.Value;
        }
    }
}

Func<T> is a factory. Each time the factory is invoked autofac will return a new instance (depending on how the lifetime of the registration is configured). Owned<T> is a light ILifetimescope, the main purpose of this class is to manage the disposal of the resolved components.

You can find more information on Func<Owned<T>> here : Combining Owned<T> with Func<T>