Search code examples
asp.net-web-apientity-framework-5castle-windsorsignalrsignalr-hub

The operation cannot be completed because the DbContext has been disposed with EF5 SignalR and Windsor Castle as IoC


I'm working on a project with the following technologies:

  • Entity Framework Version: 5
  • Entity Framework Migrations
  • SignalR version 1.1.2
  • IoC: Windsor Castle
  • Dotnet Framework 4.5
  • WebAPI
  • Visual Studio 2012
  • SQL Server Express 2012

I'm getting the error

The operation cannot be completed because the DbContext has been disposed

in the class ServerHub where I put the following:

// TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed

Anyone has any clue of why I'm getting that? I've read a lot of answers but nothing I've tried to so far seems to fix it.

EF Generic Repository (EF5)

public class EFRepository<T> : IRepository<T> where T : class
{
    public EFRepository(DbContext dbContext)
    {
        if (dbContext == null)
            throw new ArgumentNullException("dbContext");
        DbContext = dbContext;
        DbSet = DbContext.Set<T>();
    }

    protected DbContext DbContext { get; set; }

    protected DbSet<T> DbSet { get; set; }

    public virtual IQueryable<T> GetAll()
    {
        return DbSet;
    }

    public virtual IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> query = DbContext.Set<T>();
        foreach (var includeProperty in includeProperties)
        {
            query = query.Include(includeProperty);
        }

        return query;
    }

    public virtual T GetById(long id)
    {
        return DbSet.Find(id);
    }

    public virtual IQueryable<T> GetByPredicate(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
    {
        IQueryable<T> query = DbContext.Set<T>().Where(predicate);
        return query;
    }

    public virtual IQueryable<T> GetByPredicateIncluding(System.Linq.Expressions.Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> query = DbContext.Set<T>().Where(predicate);
        foreach (var includeProperty in includeProperties)
        {
            query = query.Include(includeProperty);
        }

        return query;
    }

    public virtual void Upsert(T entity, Func<T, bool> insertExpression)
    {
        if (insertExpression.Invoke(entity))
        {
            Add(entity);
        }
        else
        {
            Update(entity);
        }
    }

    public virtual void Add(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State != EntityState.Detached)
        {
            dbEntityEntry.State = EntityState.Added;
        }
        else
        {
            DbSet.Add(entity);
        }
    }

    public virtual void Update(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State == EntityState.Detached)
        {
            DbSet.Attach(entity);
        }
        dbEntityEntry.State = EntityState.Modified;
    }

    public virtual void Delete(T entity)
    {
        DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State != EntityState.Deleted)
        {
            dbEntityEntry.State = EntityState.Deleted;
        }
        else
        {
            DbSet.Attach(entity);
            DbSet.Remove(entity);
        }
    }

    public virtual void Delete(int id)
    {
        var entity = GetById(id);
        if (entity == null) return; // not found; assume already deleted.
        Delete(entity);
    }
}

HubsInstaller

using Microsoft.AspNet.SignalR;

public class HubsInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component
            .For<RepositoryFactories>()
            .ImplementedBy<RepositoryFactories>()
            .LifestyleSingleton());

        container.Register(Component
            .For<IRepositoryProvider>()
            .ImplementedBy<RepositoryProvider>()
            .LifestylePerWebRequest());

        container.Register(Component
            .For<IGdpUow>()
            .ImplementedBy<GdpUow>()
            .LifestylePerWebRequest());

        container.Register(Classes.FromThisAssembly()
            .BasedOn<Hub>()
            .LifestyleTransient());            
    }
}

IocConfig.cs

using System.Web.Routing;
using Microsoft.AspNet.SignalR;

namespace CompanyGdpSoftware.Server.Ui.Web
{
    using System.Web.Http;
    using System.Web.Mvc;
    using Castle.Windsor;
    using CommonServiceLocator.WindsorAdapter;
    using Infrastructure;
    using Microsoft.Practices.ServiceLocation;

    public static class IocConfig
    {
        public static IWindsorContainer Container { get; private set; }

        public static void RegisterIoc(HttpConfiguration config)
        {
            var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
            var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
            GlobalHost.DependencyResolver = signalrDependency;
            //RouteTable.Routes.MapHubs(signalrDependency); // Needed to remove the parameter when moved from SignalR RC to 1.1.2
            RouteTable.Routes.MapHubs(); // Used this one when moving to SignalR release update.

            // Set the dependency resolver for Web API.
            var webApicontainer = new WindsorContainer().Install(new WebWindsorInstaller());
            GlobalConfiguration.Configuration.DependencyResolver = new WebApiWindsorDependencyResolver(webApicontainer);

            // Set the dependency resolver for Mvc Controllers
            Container = new WindsorContainer().Install(new ControllersInstaller());
            DependencyResolver.SetResolver(new MvcWindsorDependencyResolver(Container)); 
            ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(Container)); 
            var controllerFactory = new WindsorControllerFactory(Container.Kernel); 
            ControllerBuilder.Current.SetControllerFactory(controllerFactory); 
        }
    }
}

UoW (Unit of Work)

public class GdpUow : IGdpUow, IDisposable
    {
        public GdpUow(IRepositoryProvider repositoryProvider)
        {
            CreateDbContext();

            repositoryProvider.DbContext = DbContext;
            RepositoryProvider = repositoryProvider;
        }

        public IRepository<Branch> Branches { get { return GetStandardRepo<Branch>(); } }

        public void Commit()
        {
            DbContext.SaveChanges();
        }

        protected void CreateDbContext()
        {
            DbContext = new GdpSoftwareDbContext();

            // Do NOT enable proxied entities, else serialization fails
            DbContext.Configuration.ProxyCreationEnabled = false;

            // Load navigation properties explicitly (avoid serialization trouble)
            DbContext.Configuration.LazyLoadingEnabled = false;

            // Because Web API will perform validation, I don't need/want EF to do so
            DbContext.Configuration.ValidateOnSaveEnabled = false;
        }

        protected IRepositoryProvider RepositoryProvider { get; set; }

        private IRepository<T> GetStandardRepo<T>() where T : class
        {
            return RepositoryProvider.GetRepositoryForEntityType<T>();
        }
        private T GetRepo<T>() where T : class
        {
            return RepositoryProvider.GetRepository<T>();
        }

        private GdpSoftwareDbContext DbContext { get; set; }

        #region IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            if (DbContext != null)
            {
                DbContext.Dispose();
                DbContext = null;
            }
        }
        #endregion
    }
}

Extract from ServerHub

    using System;
    using System.Linq;
    using System.Timers;
    using Data.Contracts;
    using Data.Model;
    using Microsoft.AspNet.SignalR;

    public class ServerHub : Hub
    {
        private static System.Timers.Timer aTimer;

        public IGdpUow Uow { get; set; }

        DateTime lastDate = DateTime.UtcNow;

        public ServerHub()
        {
            // Create a timer with a ten second interval.
            aTimer = new System.Timers.Timer(10000);

            // Hook up the Elapsed event for the timer.
            aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

            aTimer.Enabled = true;

            // If the timer is declared in a long-running method, use
            // KeepAlive to prevent garbage collection from occurring
            // before the method ends.
            GC.KeepAlive(aTimer);
        }

        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            SendNewMessage(e.SignalTime);
        }

        public void SendNewMessage(DateTime SignalTime)
        {
            // TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
            var configurationsRecord = this.Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeCheck")).SingleOrDefault();
            if (configurationsRecord == null)
            {
                throw new ApplicationException("Please set the LastDateTimeCheck value");
            }
        }

        // Called from the client
        public void GetAllMessages()
        {
            var MessagesList = Uow.Messages.GetAll().Select(
                newMessage => new MessageDto
                {
                    Country = newMessage.Country,
                    CountryId = newMessage.CountryId ?? 0,
                    MessageId = newMessage.MessageId
                });
            Clients.All.handleGetAll(MessagesList);
        }

    }

UPDATE I've added this that Drew suggested...still no luck

    using System;
    using System.Linq;
    using System.Timers;
    using Data.Contracts;
    using Data.Model;
    using Microsoft.AspNet.SignalR;

    public class ServerHub : Hub
    {
        private static System.Timers.Timer aTimer;

        DateTime lastDate = DateTime.UtcNow;

        public IHubHandler hubHandler { get; set; }

        public ServerHub()
        {
            // Create a timer with a ten second interval.
            aTimer = new System.Timers.Timer(10000);

            // Hook up the Elapsed event for the timer.
            aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

            aTimer.Enabled = true;

            // If the timer is declared in a long-running method, use
            // KeepAlive to prevent garbage collection from occurring
            // before the method ends.
            GC.KeepAlive(aTimer);
        }

        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            HubHandler.SendNewMessage(e.SignalTime);
        }


        // Called from the client
        public void GetAllMessages()
        {
            var MessagesList = Uow.Messages.GetAll().Select(
                newMessage => new MessageDto
                {
                    Country = newMessage.Country,
                    CountryId = newMessage.CountryId ?? 0,
                    MessageId = newMessage.MessageId
                });
            Clients.All.handleGetAll(MessagesList);
        }

        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            hubHandler.SendNewMessage(e.SignalTime);
        }
    }

and the new class

HubHandler

    using System;
    using System.Linq;
    using Data.Contracts;
    using Data.Model;
    using Microsoft.AspNet.SignalR;

    public class HubHandler : IHubHandler
    {
        public IGdpUow Uow { get; set; }
        DateTime lastDate = DateTime.UtcNow;

        public void SendNewMessage(DateTime signalTime)
        {
            // Get a hub context for ServerHub
            var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();

            // TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
            var gdpConfigurationRecord = Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
            if (gdpConfigurationRecord == null)
            {
                throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
            }

            var lastMessagesDateTimeCheck = gdpConfigurationRecord.DateTimeValue;

            // Send a message to all the clients
            serverHub.Clients.All.handleNewMessages("message");
            gdpConfigurationRecord.DateTimeValue = signalTime.ToUniversalTime();
            Uow.GdpConfigurations.Update(gdpConfigurationRecord);
        }
    }
}

UPDATE 2

Now I moved the timer out of the Hub to the HubHandler. Also I installed Nuget package to use LifeStyle.HybridPerWebRequestTransient for GdpUow and RepositoryProvider Still the same issue.

ServerHub

namespace GdpSoftware.App.Ui.Web.Hubs
{
    using System;
    using System.Linq;
    using Data.Contracts;
    using Data.Model;
    using Microsoft.AspNet.SignalR;

    public class ServerHub : Hub
    {
        public IGdpUow Uow { get; set; }

        public IHubHandler hubHandler { get; set; }

        public void GetAllMessages()
        {
            var messagesList = Uow.Messages.GetAll().Select(
                newMessage => new MessageDto
                {
                    MessageId = newMessage.MessageId,
                    Messagestatus = newMessage.MessageStatus.Description
                });
            hubHandler.SetClients(Clients);
            hubHandler.StartTimer();
            Clients.All.handleMessages(messagesList);
        }
    }
}

HubInstaller

public class HubsInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component
            .For<RepositoryFactories>()
            .ImplementedBy<RepositoryFactories>()
            .LifestyleSingleton());

        container.Register(Component
            .For<IRepositoryProvider>()
            .ImplementedBy<RepositoryProvider>()
        .LifeStyle.HybridPerWebRequestTransient());

        container.Register(Component
            .For<IGdpUow>()
            .ImplementedBy<GdpUow>()
            .LifeStyle.HybridPerWebRequestTransient());

        container.Register(Component
            .For<IHubHandler>()
            .ImplementedBy<HubHandler>()
            .LifestyleSingleton());

        container.Register(Classes.FromThisAssembly()
            .BasedOn<Hub>()
            .LifestyleTransient());
    }
}

HubHandler

public class HubHandler : IHubHandler
{
    private static System.Timers.Timer aTimer;
    private IHubConnectionContext Clients { get; set; }
    public IGdpUow Uow { get; set; }
    DateTime lastDate = DateTime.UtcNow;

    public void SetClients(IHubConnectionContext clients)
    {
        Clients = clients;
    }

    public void StartTimer()
    {
        aTimer = new System.Timers.Timer(10000);
        aTimer.Elapsed += new ElapsedEventHandler(SendNewMessage);
        aTimer.Enabled = true;

        //If the timer is declared in a long-running method, use KeepAlive to prevent garbage collection from occurring before the method ends.
        GC.KeepAlive(aTimer);     
    }

    public void SendNewMessage(object state, ElapsedEventArgs elapsedEventArgs)
    {
        // Get a hub context for ServerHub
        var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();

        // TODO: This is throwing the error: The operation cannot be completed because the DbContext has been disposed
        var gdpConfigurationsRecord = Uow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
        if (gdpConfigurationsRecord == null)
        {
            throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
        }

        // Send a message to all the clients
        serverHub.Clients.All.handleSendNewMessages("");
    }
}

RegisterHubs.cs

public static class RegisterHubs
{
    public static void Start()
    {
        // Register the default hubs route: ~/signalr
        var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
        var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
        GlobalHost.DependencyResolver = signalrDependency;
        RouteTable.Routes.MapHubs();
    }
}

UPDATE 3

I'm getting an error from Windsor...

If I put this as the latest line in IoC.config (which is called in Application_Start)

webApicontainer.Resolve<IHubHandler>().StartTimer();

I get:

No component for supporting the service GdpSoftware.Server.Ui.Web.Hubs.IHubHandler was found

Same thing if I remove it from IoC.config and try to use this in as the latest line in Application_Start

Container.Resolve<IHubHandler>().StartTimer();

If I add

container.Register(Component
.For<IHubHandler>()
.ImplementedBy<HubHandler>()
.LifestyleSingleton());

to ControllersInstaller I get

Can't create component 'GdpSoftware.Server.Ui.Web.Hubs.HubHandler' as it has dependencies to be satisfied. (Service 'Castle.Windsor.IWindsorContainer' which was not registered)

Where/How can I use the

Container.Resolve<IHubHandler>().StartTimer();

This is my current IoC.config

public static class IocConfig
{
    public static IWindsorContainer Container { get; private set; }

    public static void RegisterIoc(HttpConfiguration config)
    {
        var webApicontainer = new WindsorContainer().Install(new WebWindsorInstaller());
        GlobalConfiguration.Configuration.DependencyResolver = new WebApiWindsorDependencyResolver(webApicontainer);
        Container = new WindsorContainer().Install(new ControllersInstaller());
        DependencyResolver.SetResolver(new MvcWindsorDependencyResolver(Container)); 
        ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(Container));
        var controllerFactory = new WindsorControllerFactory(Container.Kernel);
        ControllerBuilder.Current.SetControllerFactory(controllerFactory); 
        webApicontainer.Resolve<IHubHandler>().StartTimer();
    }
}

and this is my Application_Start

    protected void Application_Start()
    {
        RegisterHubs.Start();
        IocConfig.RegisterIoc(GlobalConfiguration.Configuration);
        GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
        AreaRegistration.RegisterAllAreas();
        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
        GlobalConfig.CustomizeConfig(GlobalConfiguration.Configuration);
    }

I don't even understand why it should be in ControllersInstaller and not in HubInstaller...


Solution

  • Looks to me like you're injecting your UoW into the Hub, but expecting to use the instance in a timer callback. The problem is that the hub instance and its dependencies will have been logically cleaned up (e.g. disposed) after it processes the last message it received, so when the timer callback fires it's finding an already disposed instance.

    It's better to move this kind of timer based logic out of the hub into a separate class that resolves the hub context to fire messages when it needs to. So, here's an example of what the logic to get and send a message to the hub might look like in that class:

       // Get a hub context for ServerHub
       IHubContext serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
    
       // Send a message to all the clients
       serverHub.Clients.All.SendNewMessage(e.SignalTime);
    

    The trick for you will be working with the container to create a lifetime scope for when the timer fires so that you have instances of the dependencies you need specific to that one firing of the event. There are many examples of how to do this kind of stuff here on StackOverflow and the web at large, so I won't bother detailing the specifics of that here.

    UPDATE

    I did a little research into Windsor (I usually use Autofac/Ninject) so I can provide you with better sample code now. Let's start with what you've called your HubHandler class. It should be designed as a singleton which you register with the container in your installer and then resolve and later resolve at startup to start the timer. So something like this in your HubInstaller::Install:

    container.Register(Component.For<IHubHandler>()
        .ImplementedBy<HubHandler>()
        .LifestyleSingleton());
    

    Then, somewhere in the scope of your application startup after the container is installed (Application_Start if ASP.NET for example) you want to startup the timer on the registered IHubHandler:

    container.Resolve<IHubHandler>().StartTimer();
    

    Next, you would change your HubHandler class to look something like this:

    public class HubHandler : IHubHandler
    {
        System.Timers.Timer aTimer;
        DateTime lastDate = DateTime.UtcNow;
        IKernel kernel;
    
        public HubHandler(IKernel kernel)
        {
            this.kernel = kernel;
        }
    
        public void StartTimer()
        {
            aTimer = new System.Timers.Timer(10000);
            aTimer.Elapsed += new ElapsedEventHandler(SendNewMessage);
            aTimer.Enabled = true;
        }
    
        public void SendNewMessage(object state, ElapsedEventArgs elapsedEventArgs)
        {
            // Create a container specific to the scope of this timer callback to 
            // resolve dependencies from
            // NOTE: instances resolved from this container will be cleaned up when 
            // the container itself is disposed at the end of the using block
            // NOTE: you must make sure to register the types you will use here with 
            // LifestyleScoped() as well so they will be disposed of when the scope ends
            using(kernel.BeginScope())
            {
                // Resolve our IGdpUow dependency from the scoped container                
                IGdpUow gdpUow = kernel.Resolve<IGdpUow>();
    
                var gdpConfigurationsRecord = gdpUow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
    
                if (gdpConfigurationsRecord == null)
                {
                    throw new ApplicationException("Please set the LastDateTimeMessagesCheck value in GdpConfigurations");
                }
    
                // Get a hub context for ServerHub
                var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
    
                // Send a message to all the clients
                serverHub.Clients.All.handleSendNewMessages("");
           }
        }
    }
    

    Then just update your ServerHub class so it no longer knows about/does anything with the IHubHandler:

    public class ServerHub : Hub
    {
        public IGdpUow Uow { get; set; }
    
        public void GetAllMessages()
        {
            var messagesList = Uow.Messages.GetAll().Select(
                newMessage => new MessageDto
                {
                    MessageId = newMessage.MessageId,
                    Messagestatus = newMessage.MessageStatus.Description
                });
    
            Clients.All.handleMessages(messagesList);
        }
    }
    

    So first, as a disclaimer, this is a quick and dirty example just trying to get you to understand how you need to wire this stuff together. Generally having a class actually have a dependency on the IoC framework (IKernel in this case) isn't a great design. However, since this class does need to manage the lifetime scope in the callback it does need to be rather intimate with the container it's working with. You might want to look into cleaning this up a little bit.

    Second, instead of using GlobalHost.ConnectionManager directly in the callback, you might want to look into actually just resolving IConnectionManager through the container. You would obviously have to register the default ConnectionManager instance as IConnectionManager in your container and SignalR would then see/use that instance rather than falling back and creating its own. This decoupling would allow you to test the HubHandler class with a mock/fake IConnectionManager implementation which is probably desirable.