Search code examples
asp.net-web-apidependency-injectioninversion-of-controlsimple-injector

Simple Injector: Implementation that depends on http request


I'm a beginner with Simple Injector and have a scenario where I need help to implement. I will try to simplify what I need to do.

I have a WebAPI where I need to authenticate users and based on the type of user choose an implementation.

Consider this structure

public interface ICarRepository {
    void SaveCar(Car car);
}

//Some implementation for ICarRepository

public interface ICarLogic {
    void CreateCar(Car car);
}

public class CarLogicStandard: ICarLogic {
    private ICarRepository _carRepository;

    public CarLogicStandard(ICarRepository carRepository) {
        _carRepository = carRepository;
    }

    public void CreateCar(Car car) {
        car.Color = "Blue";
        _carRepository.SaveCar();
        //Other stuff...
    }
}

public class CarLogicPremium: ICarLogic {
    private ICarRepository _carRepository;

    public CarLogicPremium(ICarRepository carRepository) {
        _carRepository = carRepository;
    }

    public void CreateCar(Car car) {
        car.Color = "Red";
        _carRepository.SaveCar();
        //Other stuff 2...
    }

}

And now I have a controller

public class CarController: ApiController {
    private ICarLogic _carLogic;
    public CarController(ICarLogic carLogic) {
        _carLogic = carLogic;
    }

    public void Post(somePostData) {
        //Identify the user based on post data
        //....

        Car car = somePostData.SomeCar();
        _carLogic.CreateCar(car);
    }


}

The code above will not work because in my request I need to identify the user. If it is a premium user the controller should use the CarLogicPremium and if it is a standard user the controller should use the CarLogicStandard.

I can configure the repository and others interfaces that don't need this logic on Global.asax however, since this case I need the request to decide which implementation should be used, I supose that I need to solve this in some other way.

There is a "Simple Injector" way to handle this? Or should I try another approach?


Solution

  • The simplest solution would be to configure the decision in the composition root, along with the rest of the container's configuration:

    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
    
        var container = new Container();
    
        container.Register<CarLogicStandard>();
        container.Register<CarLogicPremium>();
        container.RegisterPerWebRequest<ICarRepository, CarRepository>();
        container.Register<ICarLogic>(
            () =>
                HttpContext.Current != null &&
                    HttpContext.Current.User != null &&
                    HttpContext.Current.User.IsInRole("Premium")
                ? (ICarLogic)container.GetInstance<CarLogicPremium>()
                : (ICarLogic)container.GetInstance<CarLogicStandard>()
            );
    
        // This is an extension method from the integration package.
        container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
    
        container.Verify();
    
        GlobalConfiguration.Configuration.DependencyResolver =
            new SimpleInjectorWebApiDependencyResolver(container);
    }
    

    You could also create an abstraction over the current user and decorate standard features with premium features

    public class CarLogicPremium : ICarLogic
    {
        private readonly ICarLogic decorated;
        private readonly ICurrentUser currentUser;
        private readonly ICarRepository carRepository;
    
        public CarLogicPremium(
            ICarLogic decorated,
            ICurrentUser currentUser,
            ICarRepository carRepository)
        {
            this.decorated = decorated;
            this.currentUser = currentUser;
            this.carRepository = carRepository;
        }
    
        public void CreateCar(Car car)
        {
            if (currentUser.IsPremiumMember)
            {
                car.Color = "Red";
                this.carRepository.SaveCar(car);
                //Other stuff 2...
            }
            else
            {
                this.decorated.CreateCar(car);
            }
        }
    }
    

    which would be configured a bit like this

    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
    
        var container = new Container();
    
        container.Register<ICurrentUser, HttpCurrentUserProxy>();
        container.RegisterPerWebRequest<ICarRepository, CarRepository>();
        container.Register<ICarLogic, CarLogicStandard>();
        container.RegisterDecorator(typeof(ICarLogic), typeof(CarLogicPremium));
    
        container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
    
        container.Verify();
    
        GlobalConfiguration.Configuration.DependencyResolver =
            new SimpleInjectorWebApiDependencyResolver(container);
    }
    

    But it really depends how many variations of services you will be creating over time. If you will be constantly adding new premium features you should look to implement a variation of the Try-X pattern. Let me know if one of the above works for you or if you need more info ...