Search code examples
c#dependency-injectionasp.net-coresimple-injector

How to access ISession when registering for DI in mvc core


I am using SimpleInjector for my DI in Mvc Core and I have a class that accepts ISession at the constructor.

public SessionAppAdminAuthorization(ISession session)

I need to register this at the DI configuration in StartUp.Configure method but I don't know how the get the scoped session variable.

container.Register<IAppAdminAuthorization>(() => {
    return new SessionAppAdminAuthorization([I Need the ISession]); },
    Lifestyle.Scoped);

Solution

  • ASP.NET Core's ISession can be accessed through the HttpContext.Session property. Since HttpContext is runtime data, the Session is as well. Runtime data should not be injected into your components' constructors, so your SessionAppAdminAuthorization should not depend on ISession directly.

    The simplest fix is to let SessionAppAdminAuthorization depend on IHttpContextAccessor instead and call IHttpContextAccessor.HttpContext.Session later on. Example:

    public class SessionAppAdminAuthorization : IAppAdminAuthorization
    {
        private readonly IHttpContextAccessor accessor;
    
        public SessionAppAdminAuthorization(IHttpContextAccessor accessor) {
            this.accessor = accessor;
        }
    
        public void DoSomethingUseful() {
            if (this.accessor.HttpContext.Session.GetBoolean("IsAdmin")) {
                // ...
            } else {
                // ...
            }
        }
    }
    

    Now you can make the registrations as follows:

    public void ConfigureServices(IServiceCollection services)
    {
        // You need to register IHttpContextAccessor.
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
        // ...
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment e, ILoggerFactory f)
    {
        container.RegisterSingleton(
            app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
    
        container.Register<IAppAdminAuthorization, SessionAppAdminAuthorization>();
    
        // ...
    }
    

    Although this will effectively solve your problem, you might want to take it up one step. In general it's better to hide framework components and abstractions like IHttpContextAccessor, HttpContext and ISession from application components. Instead the Dependency Inversion Principle guides us towards application-specific abstractions implemented by adapters that allow translating these application-specific calls onto framework components. For instance:

    // Application-specific abstraction (part of your application's core layer)
    public interface IUserContext
    {
        bool IsAdmin { get; }
    }
    
    // Adapter implementation (placed in the Composition Root of your web app)
    public class AspNetSessionUserContextAdapter : IUserContext
    {
        private readonly IHttpContextAccessor accessor;
        public AspNetSessionUserContextAdapter(IHttpContextAccessor accessor) {
            this.accessor = accessor;
        }
    
        public bool IsAdmin => this.accessor.HttpContext.Session.GetBoolean("IsAdmin");
    }
    
    // Improved version of SessionAppAdminAuthorization
    public class SessionAppAdminAuthorization : IAppAdminAuthorization
    {
        private readonly IUserContext userContext;
        // This class can now be moved to the business layer, since there's no
        // more dependency on ASP.NET.
        public SessionAppAdminAuthorization(IUserContext userContext) {
            this.userContext = userContext;
        }
    
        public void DoSomethingUseful() {
            if (this.userContext.IsAdmin) {
                // ...
            } else {
                // ...
            }
        }
    }
    

    Registration:

    public void Configure(IApplicationBuilder app, IHostingEnvironment e, ILoggerFactory f)
    {
        var accesr = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
        container.RegisterSingleton<IUserContext>(new AspNetSessionUserContextAdapter(accesr));
        container.Register<IAppAdminAuthorization, SessionAppAdminAuthorization>();
    
        // ...
    }