Search code examples
c#asp.net-mvcunity-container

Fulfilling IUserStore Dependency with Unity


I'm having some problems with getting unity to provide dependencies for my MVC controllers.

I want my ApplicationUser data and my business data in the same database and I am using code-first migrations with Entity Framework. To that end my DbContext inherits from IdentityDbContext and then implements an interface which represents my business data:

public class DealFinderDb : IdentityDbContext<ApplicationUser>, IDealFinderDb
{
    public DealFinderDb() : base("name=DealFinderConnectionString", false)
    {
    }
    public IDbSet<Deal> Deals { get; set; }
    public IDbSet<Category> Categories { get; set; }
    public IDbSet<SavedSearch> SavedSearches { get; set; }
    public static DealFinderDb Create()
    {
        return new DealFinderDb();
    }
}

public interface IDealFinderDb : IDisposable
{
    IDbSet<Deal> Deals { get; set; }
    IDbSet<Category> Categories { get; set; }
    IDbSet<SavedSearch> SavedSearches { get; set; }
    int SaveChanges();
    DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)
        where TEntity : class;
}

In my controller I need to be able to get the current user which means my controller has a dependency, not only on a IDealFinderDb but also on a UserManager. I understand that the best way to test this is to mock an IUserStore and pass that into the constructor of my controller. I have written the tests that mock both the IUserStore and the controller's HttpContext and these tests work as expected. This means my controller looks like this:

public class SavedSearchesController : Controller
{
    private readonly IDealFinderDb dealFinderDb;
    private readonly UserManager<ApplicationUser> userManager;

    public SavedSearchesController(IDealFinderDb dealFinderDb, IUserStore<ApplicationUser> userStore)
    {
        this.dealFinderDb = dealFinderDb;
        this.userManager = new UserManager<ApplicationUser>(userStore);
    }

    public ActionResult Index()
    {
        var user = this.userManager.FindById(this.User.Identity.GetUserId());
        var usersSavedSearches = this.dealFinderDb.SavedSearches.Where(s => s.User.Id == user.Id);
        return this.View(usersSavedSearches);
    }

    // Snip unrelated action methods.

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.dealFinderDb.Dispose();
        }
        base.Dispose(disposing);
    }
}

This seems fine but I am using Unity to provide implementations for these interfaces at run-time and this is where I'm stuck. My first attempt at my UnityConfig looks like this:

container.RegisterType<IDealFinderDb, DealFinderDb>();
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(
    new InjectionConstructor(typeof(DealFinderDb)));

...but the problem with that is I end up with a DbContext being instantiated twice leading to an error of "System.InvalidOperationException: 'An entity object cannot be referenced by multiple instances of IEntityChangeTracker.'" when I call Add() on any of my IDBSets in my DbContext I guess this is because unity is instantiating my DbContext twice.

So my next attempt was to ensure that only a single instance of DealFinderDb is created and that looks like this in my UnityConfig:

container.RegisterType<DealFinderDb, DealFinderDb>(new ContainerControlledLifetimeManager());
container.RegisterType<IDealFinderDb, DealFinderDb>();
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(
    new InjectionConstructor(typeof(DealFinderDb)));

...but when this.userManager.FindById() is called in my controller I get the error "System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.'". Obviously I could avoiding calling Dispose on my Context but this is bad as I assume means I am actually using the same DBContext instance for the entire life-cycle of my application.

What should I put in my UnityConfig to ensure that both the IDealFinderDb and IUserStore dependencies are satisfied and that only a single context is instantiated each time my controller is instantiated?

Thanks


Solution

  • What should I put in my UnityConfig to ensure that both the IDealFinderDb and IUserStore dependencies are satisfied and that only a single context is instantiated each my controller is instantiated?

    You should use per-graph lifetime manager which is called PerResolveLifetimeManager in Unity:

    container.RegisterType<IDealFinderDb, DealFinderDb>(new PerResolveLifetimeManager());