Search code examples
c#dependency-injectioninversion-of-controlninject

c# repository pattern how to access values from a dependency injected class


I have a c# aspnet mvc (not core) project where Im using repository pattern and dependency injection (ninject).

This is a sample of a controller:

public class MainController : Controller
{
    ISecurityRepository _securityRepo = default(ISecurityRepository);
    AppState _appState;

    public MainController(ISecurityRepository securityRepo)
    {
        _securityRepo = securityRepo;
        _appState = (AppState)Session["appstate"];
    }

    public ActionResult Employee(int employeeId)
    {
        var permissions = _securityRepo.GetEmployeePermissions(
            employeeId,
            _appState.userId,
            _appState.sessionId);

        return View(permissions);
    }

    public ActionResult Leave(int leaveId, int employeeId)
    {
        var permissions = _securityRepo.GetEmployeeLeavePermissions(
                leaveId,
                employeeId,
                _appState.userId,
                _appState.sessionId);

        return View(permissions);
    }

    public ActionResult Holiday(int holidayId, int employeeId)
    {
        var permissions = _securityRepo.GetEmployeeHolidayPermissions(
            holidayId,
            employeeId,
            _appState.userId,
            _appState.sessionId);

        return View(permissions);
    }
}

ISecurityRepository is being injected and thats working perfect, but what I want to do is like to get in my SecurityRepository the _appState class values without the need to pass into every method userId and companyId every time.

This is my SecurityRepository.cs

public class SecurityRepository : ISecurityRepository
{
    public EmployeePermissions GetEmployeePermissions(int employeeId, int userId, int sessionId)
    {
        // ... some code here
        return employeePermissions;
    }

    public EmployeeLeavePermissions GetEmployeeLeavePermissions(int leaveId, int employeeId, int userId, int sessionId)
    {
        // ... some code here
        return employeeLeavePermissions;
    }

    public EmployeeHolidayPermissions GetEmployeeHolidayPermissions(int holidayId, int employeeId, int userId, int sessionId)
    {
        // ... some code here
        return employeeHolidayPermissions;
    }

    // ... some more methods

}

What I would like to do is to have something like this:

public class SecurityRepository : ISecurityRepository
{
    // here have maybe a constructor that receives the AppState variable from MainController, or a public property

    public EmployeePermissions GetEmployeePermissions(int employeeId)
    {
        // a way that I can user _appState here!!
        return employeePermissions;
    }
}

The injection is done inside NinjectWebCommon class

/// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind(typeof(ISecurityRepository)).To(typeof(Data.SecurityRepository));


        }

Any advice or sample code that I can follow?


Solution

  • So, you just need to somehow pass value from session to you repository. Actually there is a lot of ways to achive this.

    One way would be a some-kind-of Initialize method in your repository class (and in your repository interface, ofcourse):

    public class SecurityRepository : ISecurityRepository
    {
        public void Initialize(AppState appState)
        {
            _userId = appState.userId;
            _sessionId = appState.sessionId;
        }
    
        // some other methods
    }
    

    Then you may call it in your controller constructor:

    public MainController(ISecurityRepository securityRepo)
    {
        _securityRepo = securityRepo;
        _securityRepo.Initialize(Session["appstate"] as AppState);
    }
    

    Another way - you can handle setting your dependency in the action filter. Just add some kind of accessor:

    public class AppStateAccessor
    {
        public AppState AppState { get; set; }
    }
    

    Then set it in your filter:

    public class RequestScopeDependencySetupFilter : ActionFilterAttribute
    {
        private readonly IKernel _kernel;
    
        public RequestScopeDependencySetupFilter(IKernel kernel)
        {
            _kernel = kernel;
        }
    
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            var accessor = _kernel.Get<AppStateAccessor>();
            accessor.AppState = context.HttpContext.Session["appstate"] as AppState;
        }
    }
    

    Then register this filter as global:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        var kernel = /* get your IKernel somehow */
        filters.Add(new RequestScopeDependencySetupFilter(kernel));
    }
    

    And finally use it in your repository:

    public class SecurityRepository : ISecurityRepository
    {
        public SecurityRepository(AppStateAccessor accessor)
        {
            _userId = accessor.AppState.userId;
            _sessionId = accessor.AppState.sessionId;
        }
    
        // some other methods
    }
    

    Just don't forget to bind your dependencies right. The key point of this method is that your AppStateAccessor instance is transient and live in request scope. Ninject (specificly Ninject.Extensions.ContextPreservation extension) allows you to bind instance of object in DI container for a request scope:

    private static void RegisterServices(IKernel kernel)
    {
        // bind AppStateAccessor in request scope
        kernel.Bind<AppStateAccessor>().ToSelf().InRequestScope();
    
        // transient repository
        kernel.Bind<ISecurityRepository>().To<Data.SecurityRepository>();
    
        // other dependencies...
    
    }
    

    And one more way. Just use HttpContext.Current static property in your repository:

    public class SecurityRepository : ISecurityRepository
    {
        public SecurityRepository()
        {
            var appState = HttpContext.Current.Session["appstate"] as AppState;
    
            _userId = appState.userId;
            _sessionId = appState.sessionId;
        }
    
        // some other methods
    }
    

    Easy to implement, but add a hard static dependency to your repository - forget about unit testing.


    Just one more way (which is a combination of 2nd and 3rd, thanks @Nkosi) simular to this answer. Extract interface from AppStateAccessor:

    public interface IAppStateAccessor
    {
        AppState AppState { get; }
    }
    

    Then change AppStateAccessor a bit:

    public class AppStateAccessor
    {
        public AppState AppState { get; }
            = HttpContext.Current.Session["appstate"] as AppState;
    }
    

    Then use this interface in your repository:

    public class SecurityRepository : ISecurityRepository
    {
        public SecurityRepository(IAppStateAccessor accessor)
        {
            _userId = accessor.AppState.userId;
            _sessionId = accessor.AppState.sessionId;
        }
    
        // some other methods
    }
    

    This time your service is fully decoupled from HttpContext and can be easily unit-tested.