5

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!

rrejc
  • 2,682
  • 3
  • 26
  • 33
  • 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 Mar 17 '15 at 01:26
  • 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 Mar 17 '15 at 01:35
  • @TheVedge: I agree with you. Suggested style (passing container to constructor) is from documentation: http://autofac.readthedocs.org/en/latest/integration/signalr.html#managing-dependency-lifetimes – rrejc Mar 19 '15 at 04:34

2 Answers2

4

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.

Ric .Net
  • 5,540
  • 1
  • 20
  • 39
  • @MattKocaj I edited the answer back because Lifestyle is the correct terminology when it comes to lifetime management in Dependency Injection. – Ric .Net Nov 03 '17 at 10:28
  • "Lifestyle" is not the correct term. Just google "Dependency injection" and you'll find the terms used are ["lifetime"](https://en.wikipedia.org/wiki/Dependency_injection) and ["lifecycle"](https://stackoverflow.com/questions/130794/what-is-dependency-injection). "Lifestyle" is a typo I think. – Matt Kocaj Nov 04 '17 at 08:34
  • @MattKocaj The [DI bible](https://www.manning.com/books/dependency-injection-in-dot-net) refers to lifetime management using different 'lifestyles'. – Ric .Net Nov 04 '17 at 09:51
  • While Seemann uses "*style" for his collection of patterns, said patterns (singleton, transient, etc) might be implemented with slight differences from container to container. This question is about Autofac, so for the context specific to that library, and for the sake of other readers who haven't read Seemann's book, I suggest you use a terminology which is most relevant and will help the wider audience, which IMO is "lifetime" or "lifecycle" - terms that the Autofac doco readily uses also. You won't find "lifestyle" in https://autofac.readthedocs.io and I find that to be a problem. – Matt Kocaj Nov 05 '17 at 03:50
  • 1
    @MattKocaj: I disagree with you. Seemann's book has become the de facto standard reference for patterns around DI. So I would urge to keep using the common terminology, just as we would keep using commonly used pattern names such as Decorator and Proxy. – Steven Nov 05 '17 at 15:22
1

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>

Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
  • Downside of the `Owned` interface is that your application code takes a dependency on the DI library, which is something you typically want to prevent IMO. – Steven Mar 16 '15 at 22:28
  • @Steven I agree with you. It is possible to resolve `Func` but there will be no guarantee that a direct or indirect disposable dependency of `DbContext` will be disposed when `DbContext` is disposed. As far as I know there is no native .net equivalent to `Owned`. I edited the post to better explain `Owned` – Cyril Durand Mar 16 '15 at 22:54
  • Perhaps I misunderstand how Autofac works, but the use of `Owned` seems superfluous. If the `DbContext` is registered as `InstancePerLifetimeScope`, Autofac should automatically dispose that `DbContext`instance when its surrounding lifetime scope ends. In other words, it should be enough to inject an `Func`. Problem however with Autofac is that in case there is no active lifetime scope, the `DbContext` is resolved as singleton (a design flaw IMO). That makes it a little tricky. – Steven Mar 16 '15 at 23:01
  • `Owned` is superfluous if the lifetime of these objects are the same. But if the owner of the factory is a singleton, objects graph created by the factory *won't* be disposed (*GC* may dispose them). In this case, it seems that `ChatService` is a singleton. – Cyril Durand Mar 17 '15 at 08:01
  • I'm sorry. My assumption was that would work, because `Func` is a singleton, just as `ChatService`. My assumption was that the `Func` would produce a new instance based on the lifestyle, but after some experimenting, I have to conclude that the `Func` behaves differently when injected into a singleton. A `Func` in Autofac produces a singleton when injected into a singleton, even when the `DoSomething` is called in the _context_ of a lifetime scope. This is REALLY confusing IMO. But this explains why you have to take a dependency on `Owned` in Autofac's case. – Steven Mar 17 '15 at 09:17
  • Autofac `Func` implementation doesn't always produces singleton inside a singleton. It depends on how the lifetimescope is configured I created this [dotnetfiddle](https://dotnetfiddle.net/cJdJhf) for demonstration. By the way without `Owned` how do you manage disposal of component created by factory ? ([see here for example](https://dotnetfiddle.net/CYlVE7)) – Cyril Durand Mar 17 '15 at 10:22
  • I can't answer the question about "how do you manage disposal of component created by factory" in the context of Autofac. With Simple Injector it is dead simple. You don't have to do anything. Simple Injector will resolve it for you from an active scope (and will throw an exception if there is no scope) and Simple Injector will dispose your instance when the scope ends. Scopes are ambient (just like TransactionScope) and don't have to be passed around. This makes life much easier. – Steven Mar 17 '15 at 10:42