Autofac(+ MVC + EF + SignalR + Hangfire)生命周期范围

时间:2015-03-16 20:17:26

标签: asp.net-mvc entity-framework signalr autofac hangfire

我有一个ASP.NET MVC项目,它使用Entity Framwork,SignalR和Hangfire作业。

我的主(根)容器以这种方式定义:

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();

对于MVC我正在使用Autofac.MVC5 nuget包。依赖性解析器:

DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

对于SignalR我正在使用Autofac.SignalR nuget包。依赖性解析器:

GlobalHost.DependencyResolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(container);

我的signalR hub以这种方式实例化(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);
}

对于Hangfire我正在使用Hangfire.Autofac包:

config.UseActivator(new AutofacJobActivator(container));

以这种方式实例化工作:

private readonly ILifetimeScope _jobScope;
protected IChatService ChatService;
protected BaseJob(ILifetimeScope scope)
{
    _jobScope = scope.BeginLifetimeScope();
    ChatService = _jobScope.Resolve<IChatService>();
}
public void Dispose()
{
    _jobScope.Dispose();
}

问题/问题: 我总是在集线器和作业中获得相同的DbContext实例。我希望所有集线器实例都能获得相同的ChatService,但DbContext(它是ChatService的依赖项)将始终是一个新实例。 Hangfire的工作也应该是一样的。

可以这样做,还是我错过了什么?

更新1:

在思考(睡过头)后,我想我有两个选择。我仍然希望保留“每个请求的会话”(“每个集群的会话”,“每个作业的会话”)。

选项1:

更改所有服务将具有InstancePerLifetimeScope。实例化服务并不昂贵。对于维护某种状态的服务,我将创建另一个“存储”(类),它将是SingleInstance并且不依赖于session(DbContext)。我认为这也适用于枢纽和工作。

选项2:

创建@Ric .Net建议的某种工厂。像这样:

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();
        }
    }

我认为这对MVC有用,但我不知道热点让它适用于集线器(每个集线器调用是集线器的新实例)和作业(每次运行的作业都是一个新的实例)工作班)。

我倾向于选项1.你怎么看?

非常感谢!

2 个答案:

答案 0 :(得分:4)

我对AutoFac完全缺乏经验。但引起我注意的是:

  

我希望所有集线器实例都能获得相同的ChatService,但DbContext(它是ChatService的依赖项)将始终是一个新实例。

你在这里基本上说的是:

“我的汽车是由依赖车库的汽车公司进行维修的,但每次我带车时都希望车库成为新车”。

当您在其他组件中注入(完全构建实例,包括依赖项)ChatService时,当然还会构建其他依赖项,无论它们是否具有其他类型的生活方式。当创建一个比它注入的对象更短的生命周期的对象时,你创建了一个所谓的“captive dependency

DbContext中获取ChatService的新“实例”的唯一方法不是注入DbContext本身,而是注入DbContextFactory来创建无论何时使用它都会DbContext

实现看起来像:

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    
         }
     }
}

DbContextFactory可以使用Singleton Lifestyle在AutoFac中注册。

然而,这可能不是你的目标。因为在这种情况下每次你使用DbContext就会得到一个新的。另一方面,新的DbContext可能是最安全的方法,因为你可以阅读here

这个好的答案值得一读,原因不止一个,因为它解释了如何使用command / handler pattern,这应该非常适合你的情况。

这会让您的聊天服务完全不了解DbContext,这会改善您的应用程序的“SOLID”设计,并创建测试ChatService的可能性,这实际上是可以撤销的直接注入DbContextDbContextFactory时。

答案 1 :(得分:1)

您需要解决工厂问题。 Autofac 内置支持Func<T>,例如Dynamic instantiation

如果您的依赖项具有一次性依赖项,则必须管理dispose模式以避免内存泄漏。使用 Autofac 解决此问题的常见模式是使用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>工厂。每次调用 factory 时,autofac都将返回一个新实例(具体取决于注册生命周期的配置方式)。 Owned<T>是一个轻型ILifetimescope,此类的主要目的是管理已解析组件的处理。

您可以在此处找到有关Func<Owned<T>>的更多信息:Combining Owned<T> with Func<T>