Search code examples
c#asp.netasp.net-identitystructuremap

StructureMap DbContext with identity


So i have recently started using structure map. I have a web api using identity. My goal is to have a dbcontext per request. Currently i am having context issues, the context injected through structure map is different to the one being used by the user manager. I have looked around and cant find a solution even though im sure its pretty obvious.

So to start with i have my structuremap setup.

public class DefaultRegistry : Registry {
    #region Constructors and Destructors

    public DefaultRegistry() {
        Scan(
            scan => {
                scan.TheCallingAssembly();
                scan.AddAllTypesOf<IImporterService>().NameBy(type => type.Name);
                scan.WithDefaultConventions();
            });

        For<IHttpContextBaseWrapper>().Use<HttpContextBaseWrapper>();
        For<ApplicationDbContext>().Use(() => new ApplicationDbContext());
        For<HttpContextBase>().Use(() => new HttpContextWrapper(HttpContext.Current));
    }

    #endregion
}

I do not need to inject the user manager for the moment as its accessed through the httpcontext in a base service, i know this will probably have to change for unit testing in the future.

Next we have the startup auth.

private void ConfigureOAuthTokenGeneration(IAppBuilder app)
    {
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

        //Token Auth
        var OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/api/account/login"),
            Provider = new ApplicationOAuthProvider("self"),
            AuthorizeEndpointPath = new PathString("/api/AccountApi/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(121),
            RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
            //#if DEBUG
            AllowInsecureHttp = true,
            //#endif
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
    }

I have cut down the code but i have the typical structure of service and repository layers. All inject using structuremap, the service and repositories have the correct context, the only on that does not is the user manager. I know its the line below causing the issue:

app.CreatePerOwinContext(ApplicationDbContext.Create);

However like i said i am still getting used to structure map, i previously used unity and would just resolve the instance i need from the current container. Any help or guidance is much appreciated.


Solution

  • Your problem with all these lines:

        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    

    First you tell a middleware to manage creation of your DbContext. Then you tell middleware to manage creation of your ApplicationUserManager and ApplicationUserManager.Create takes an instance of ApplicationDbContext managed by that middleware, not by your DI container. Same happens for your ApplicationRoleManager. At the end you have 2 ways of producing ApplicationDbContext per request - one through your DI container, another through middleware.

    If you want to have only single instance of ApplicationDbContext you need to get rid of the second way of creation its' instance: ApplicationDbContext.Create. This will resolve your problem.

    However your middleware still needs access to your ApplicationUserManager. So instead of all these:

       app.CreatePerOwinContext(ApplicationDbContext.Create);
       app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
       app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    

    you need to use app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());

    And get rid of all other ways of creating these instances other than injection from your DI container.

    Further info in my old blog post - scroll to "Clean up" part for the details about middleware registration.