4

CCI am writing a facade to get data from different sources, normalize, and format it. I am new to using asp.net 5 and giving dependency injection a go but I am having an issue. I want to know how to resolve dependencies based on runtime input. Based on the route I want to instantiate the correct repository. For instance if I get passed Toyota I have want to instantiate a ToyotaRepository, if I get passed Ford I want to instantiate a FordRepository. Those repositories also have dependencies that are unique to each repository. All the repositories share the same ICarRepository Interface, and depend on the same interfaces but different concrete implementations. I thought about using a factory to create the repositories but then the dependencies of each repository would have to be injected into the factory, and that just doesn't feel right. As the number of repositories grow so with the number of dependencies that will need to be injected. Currently I am just newing up the repositories and their dependencies in the factory which also feels wrong, not very SOLID. Maybe there is an issue with my architecture?

[Route("api/v1/[controller]")]
public class CarsController : Controller
{
    private IDataFormatter<Product> _formatter;
    private ILogger _logger;
    private ICarRepositoryFactory _repositoryFactory;

    public CarssController(ILogger<CarsController> logger, IProductRepositoryFactory repositoryFactory, IDataFormatter<Car> formatter)
    {
        _logger     = logger;
        _repositoryFactory = repositoryFactory;
        _formatter  = formatter;
    }

    [HttpGet("{carType}")]
    public async Task<IEnumerable<Car>> GetCars(string carType)
    {
        var repository = _repositoryFactory.Create(carType);
        var cars = await repository.GetAll();
        foreach(var car in cars)
        {
            _formatter.Format(car);
        }
        return cars;
    }
}

public class CarRepositoryFacotry : ICarRepositoryFactory
{
    private Dictionary<string, Func<ICarRepository>> _carRepositories = new Dictionary<string, Func<ICarRepository>>();
    private ILogger<ICarRepository> _logger;
    private IOptions<WebOptions> _webOptions;
    private IOptions<DisplayInfoOptions> _displayOptions;

    public CarRepositoryFacotry(ILogger<ICarRepository> logger, IOptions<WebOptions> webOptions, IOptions<DisplayInfoOptions> displayInfoOptions)
    {
        _logger = logger;
        _webOptions = webOptions;
        _displayInfoOptions = displayInfoOptions;

        _carRepositories.Add("toyota", () => new ToyotaRepository(_logger, new DisplayInfoRepository(_displayInfoOptions), new ToyotaMapper(), _options));
        _carRepositories.Add("ford", () => new FordRepository(_logger, new DisplayInfoRepository(_displayInfoOptions), new FordMapper(), _options));
    }
    public ICarRepository Create(string carType)
    {
        Func<ICarRepository> repo;
        _carRepositories.TryGetValue(carType, out repo);
        return repo.Invoke();
    }
}

I am currently using the builtin dependency framework in asp.net 5 but Im willing to use autofac if it makes things eaisier. Any help or comments would be a big help.

jeffreyk
  • 123
  • 1
  • 8
  • I have never used the builtin framework, but in autofac you can create a factory which depends on `Func`. This allows you to request a `FordRepository` when needed at runtime without needing the dependencies of `FordRepository`, autofac will take care of that. – wimh Jan 19 '16 at 20:57
  • And in autofac you can use [Named and Keyed Services](http://docs.autofac.org/en/stable/advanced/keyed-services.html), which might be useful here. – wimh Jan 19 '16 at 21:02
  • @Wimmel if I used named and keyed services in autofac I'd have to inject the container in the my class. I thought that having a dependency on a IoC container like that was bad practice, akin to the service locator pattern. I assume you the factories in autofac you are talking about are http://docs.autofac.org/en/stable/advanced/delegate-factories.html – jeffreyk Jan 19 '16 at 23:04
  • Agreed about the dependency on the container. But in some cases there is no alternative (I am not sure about this situation). In autofac you can register custom factories, for example http://stackoverflow.com/a/2888939/33499. So you could have a dependency on `Func`, which is a wrapper around the `Named<>` registration. – wimh Jan 21 '16 at 11:49

3 Answers3

2

Using factory with all repositories injected is feasible approach ( and much better than temporary "new-ing" dependencies )

example

public interface IVehicleRepository
{
    bool CanHandle(string vendor); // example how to deal with choosing appropriate repository

    IEnumerable<Vehicle> GetAll();
}

public class VehicleFactory
{
    private readonly IVehicleRepository[] repositories;

    public VehicleFactory(IVehicleRepository[] repositories)
    {
        this.repositories = repositories;
    }

    public IVehicleRepository Create(string vendor) {
        return repositories.Single(r => r.CanHandle(vendor));
    }
}

usage:

[Route("api/v1/[controller]")]
public class CarController : Controller
{
    private readonly VehicleFactory factory;

    public CarController(VehicleFactory factory)
    {
        this.factory = factory;
    }

    [HttpGet("{vehicleType}")]
    public IEnumerable<Vehicle> GetVehicles(string vehicleType)
    {
        var repository = factory.Create(vehicleType);
        var vehicles = repository.GetAll();
        foreach (var vehicle in vehicles)
        {
            // Process(vehicle)
        }
        return vehicles;
    }
}
Tomas
  • 675
  • 8
  • 17
0

I see it in that way:

  1. Your CarsController take ICarRepository as a constructor parameter and work with it
  2. You have to wright and register your own IControllerFactory which will analyze route parameters and create concrete instance of Controller with concrete repository

First link at Google. May be not the best, but good.

http://www.codeproject.com/Articles/560798/ASP-NET-MVC-Controller-Dependency-Injection-for-Be

Ivan
  • 312
  • 1
  • 4
  • 14
0

My team uses Castle Windsor, an IoC container that can resolve all our dependencies with ease. (Should be similar to Autofac, but I've seen Castle Windsor more often in enterprise apps)

In your case, you can

1. Register FordRepository like this:

public class RepositoriesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Classes.FromThisAssembly(),
            Component.For<DisplayInfoRepository>().ImplementedBy<DisplayInfoRepository>
              .DependsOn(Dependency.OnValue("_displayInfoOptions", displayInfoOptionsObject)),
                                           // whatever property name you have in DisplayInfoRepository

            Component.For<ICarRepository>().ImplementedBy<FordRepository>().Named("Ford")
              .DependsOn(Dependency.OnComponent(typeof(Logger), nameof("Logger")))
              .DependsOn(Dependency.OnComponent(typeof(DisplayInfoRepository), nameof(DisplayInfoRepository)))
              .DependsOn(Dependency.OnComponent(typeof(FordMapper), nameof(FordMapper)))
              .DependsOn(Dependency.OnValue("_option", optionObject)),
                                        // what ever property name you have in FordRepository
        );
    }
}

2. Start up the container:

// application starts...
var container = new WindsorContainer();
container.Install(FromAssembly.This());

// clean up, application exits
container.Dispose();

3. Get your car repositories based on strings like this

var carRepo = container.Resolve<ICarRepository>("Ford");

Let me know if any questions! Upvotes are greatly appreciated!

Hao Zhang
  • 177
  • 6