简体   繁体   中英

Is there any way to configure owin startup.cs to be run before global asax application start?

I'm trying to implement the container per request and transaction per request patterns in MVC 5. I've written most of the code for it leveraging the global asax. I am having trouble with multiple contexts being created though because owin is creating new DbContext classes as part of it's startup.cs. Is there any way I can have owin run before the global asax application start event so that I can retrieve the existing context that was created there? I'm new to these patterns so if that question doesn't sound right I'm open to alternate suggestions.

public IContainer Container
{
    get
    {
        return (IContainer)HttpContext.Current.Items["_Container"];
    }
    set
    {
        HttpContext.Current.Items["_Container"] = value;
    }
}

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    ////Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());


    DependencyResolver.SetResolver(
        new StructureMapDependencyResolver(() => Container ?? IoC.Container));

    IoC.Container.Configure(cfg =>
    {
        cfg.AddRegistry(new StandardRegistry());
        cfg.AddRegistry(new ControllerRegistry());
        cfg.AddRegistry(new ActionFilterRegistry(
            () => Container ?? IoC.Container));
        cfg.AddRegistry(new MvcRegistry());
        cfg.AddRegistry(new TaskRegistry());
        cfg.AddRegistry(new ModelMetadataRegistry());
    });

    using (var container = IoC.Container.GetNestedContainer())
    {

        foreach (var task in container.GetAllInstances<IRunAtInit>())
        {
            task.Execute();
        }

        foreach (var task in container.GetAllInstances<IRunAtStartup>())
        {
            task.Execute();
        }
    }
}

public void Application_BeginRequest()
{
    Container = IoC.Container.GetNestedContainer();

    foreach (var task in Container.GetAllInstances<IRunOnEachRequest>())
    {
        task.Execute();
    }
}

public void Application_Error()
{
    foreach (var task in Container.GetAllInstances<IRunOnError>())
    {
        task.Execute();
    }
}

public void Application_EndRequest()
{
    try
    {
        foreach (var task in
            Container.GetAllInstances<IRunAfterEachRequest>())
        {
            task.Execute();
        }
    }
    finally
    {
        Container.Dispose();
        Container = null;
    }
}

Structure Map Class

public class StructureMapDependencyResolver : IDependencyResolver
{
    private readonly Func<IContainer> _factory;

    public StructureMapDependencyResolver(Func<IContainer> factory)
    {
        _factory = factory;
    }

    public object GetService(Type serviceType)
    {
        if (serviceType == null)
        {
            return null;
        }

        var factory = _factory();

        return serviceType.IsAbstract || serviceType.IsInterface
            ? factory.TryGetInstance(serviceType)
            : factory.GetInstance(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return _factory().GetAllInstances(serviceType).Cast<object>();
    }

Startup for Owin - this is being executed both after Global ASAX on start and on each request, but after the global ASAX code. I would like to be able to either set the OWIN context to an existing context instance or have this code execute first and get the context that was created in startup.

    // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {

        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });            
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

        // Uncomment the following lines to enable logging in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");

        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");

        //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
        //{
        //    ClientId = "",
        //    ClientSecret = ""
        //});
    }
}

With MVC 5, OWIN will always run after global.asax Application_Start() . That's because the MVC application hosts OWIN. It's far easier to let OWIN handle configurations and startups, with App_Start just for MVC registrations. So I recommend moving your container registrations and startups to Startup . Something like this:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        DependencyResolver.SetResolver(
            new StructureMapDependencyResolver(IoC.Container));

        IoC.Container.Configure(cfg =>
        {
           ...
        });

        using (var container = IoC.Container.GetNestedContainer())
        {
            foreach (var task in container.GetAllInstances<IRunAtInit>())
            {
                task.Execute();
            }

            foreach (var task in container.GetAllInstances<IRunAtStartup>())
            {
                task.Execute();
            }
        }
    }
}

Then, in the Application_BeginRequest , Application_EndRequest , and Application_Error , you access the container via the DependencyResolver. eg:

public void Application_BeginRequest()
{
    foreach (var task in DependencyResolver.Current.GetServices<IRunOnEachRequest>())
    {
        task.Execute();
    }
}

Notice there is no more Container property and subsequently no nested containers (except that one in startup). That's because your question is more nuanced than just pipeline timings. The container and transaction per request patterns are really about lifecyles (specifically Transient lifecycles) which, in ASP.NET, the container already knows to resolve per request. So you don't need to duplicate that effort. The StructureMap documentation explains it better than I can.

Side note : There is a PreApplicationStart attribute you can put on an assembly which indicates what method to run before Application_start, but because it's a static method, it's really only good for static configurations - which an IoC container is not.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM