1

i have XMLProductRepository and SQLProductRepository. now how could i switch between them dynamically. i am new in DI. so searching google for the same and found a link which discuss a bit. but still do not understand on what basis the repository will be changed and how. here is the code

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product Get(int id);
    Product Add(Product item);
    void Remove(int id);
    bool Update(Product item);
}

public class XMLProductRepository : IProductRepository
{

    public XMLProductRepository() {}
    public IEnumerable<Product> GetAll() {}
    public Product Get(int id) {}
    public Product Add(Product item) {}
    public void Remove(int id) {}
    public bool Update(Product item) {}
}

public class SQLProductRepository : IProductRepository
{
    public SQLProductRepository() {}
    public IEnumerable<Product> GetAll() {}
    public Product Get(int id) {}
    public Product Add(Product item) {}
    public void Remove(int id) {}
    public bool Update(Product item) {}
} 

Unity.Mvc3 is using as Di

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();
        //Register the repository
        container.RegisterType<IProductRepository, SQLProductRepository>();
        return container;
    }
}

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
        Bootstrapper.Initialise();

    }

public class HomeController : Controller
{
    private readonly IProductRepository productRepository;

    public HomeController(IProductRepository productRepository)
    {
        this.productRepository = productRepository;
    }

i understand the code that dynamically SQLProductRepository instance is getting injecting into controller. so my question is how to inject XMLProductRepository ?

i want to design something in such a way based on url dependency will be injected. how to achieve it. looking for guide line. thanks

Mou
  • 15,673
  • 43
  • 156
  • 275
  • 1
    Why do you need switching dynamically between the two repositories? – Steven Sep 14 '15 at 15:25
  • With @Steven. This is entirely not what dependency injection is for. – CodeCaster Sep 14 '15 at 15:42
  • @CodeCaster: Yes, but in practice I find it really unlikely to have to change the storage mechanism of of an entity *at runtime*. More likely is the need to set the storage mechanism at application startup, or add cross-cutting concerns (such as logging and audit trailing) by wrapping implementations with decorators or interceptors at runtime. – Steven Sep 14 '15 at 15:50
  • @Steven it looks like OP actually doesn't need the repositories to be interchangeable. You'd usually need that for unit testing. Here, using DI to inject both `IXmlRepository` and `IDatabaseRepository` seems like a more sensible approach. – CodeCaster Sep 14 '15 at 16:09
  • @CodeCaster, I would rather say that the SqlProductRepository should be configured in your container (if any), while the XmlProductRepository should be injected by hand in the unit tests that require it. – Steven Sep 14 '15 at 16:17
  • Tou should not use a DI container in your unit tests; perhaps that's what's causing the confusion here. – Steven Sep 14 '15 at 16:18
  • @Steven absolutely, but OP has one URL that should read its data from XML while another URL should read from the database. You don't want your container involved in that, other than through the two interfaces I proposed. – CodeCaster Sep 14 '15 at 16:19

2 Answers2

2

One possible solution is to inject an IProductRepositoryFactory instead of IProductRepository itself. It would look like this:

interface IProductRepositoryFactory
{
    IProductRepository GetRepository(string url);
}

Then your HomeController would look like this:

public class HomeController : Controller
{
    private readonly IProductRepositoryFactory productRepositoryFactory;

    public HomeController(IProductRepositoryFactory productRepositoryFactory)
    {
        this.productRepositoryFactory = productRepositoryFactory;
    }
}

This way, you'll be able to get required implementation of IProductRepository in your controller action at runtime — all you need is to implement the required logic in the IProductRepositoryFactory.GetRepository(url) method.

Here's a controller action example (note that getting current request URL in such a way makes this method less testable):

public Product Get(string id)
{
    return productRepositoryFactory
          .GetRepository(Request.Url.ToString())
          .GetById(id);
}

UPD: The following is an example implementation of IProductRepositoryFactory. Just implement your own decision-making logic that returns an appropriate instance of IProductRepository based on the URL:

public class ProductRepositoryFactory : IProductRepositoryFactory
{
    public IProductRepository GetRepository(string url)
    {
        if (url.Contains("xml")) { return new XMLProductRepository(); }
        if (url.Contains("sql")) { return new SQLProductRepository(); }
        throw new ArgumentException("url");

    }
}
Sergey Kolodiy
  • 5,829
  • 1
  • 36
  • 58
  • thanks for reply but looking at this code `productRepositoryFactory .GetRepository(Request.Url.ToString()).GetById(id);` i have no idea how it will work. would u mind to post small but full working as a result based on url right dependency will be injected. – Mou Sep 15 '15 at 08:32
  • @Mou I've added an example implementation of `IProductRepositoryFactory`, please have a look. – Sergey Kolodiy Sep 15 '15 at 08:44
1

I don't know where you got the code from, perhaps this question, but the two implementations of IProductRepository that you show have two purposes.

  • SQLProductRepository will read and write data from and to the database.
  • XMLProductRepository can read XML and maybe write files.

When running code in a unit test, you generally don't want to connect to a database, but you do sometimes want to use data in a unit test. That's where the XML repository comes in handy. You prepare a data set in XML files that you can commit to version control, you inject another implementation of the requested interface - namely one that reads the XML file - and you don't need a database anymore.

That's why you configure your DI container to inject the SQLProductRepository, while in unit tests you or the DI container will provide an XMLProductRepository when the application requests an IProductRepository.

Now if you say that your controller, your business logic, is to choose SQLProductRepository for one particular request, based on the URI, and XMLProductRepository for the other, then using IProductRepository for that purpose is wrong. That is a problem that should not be solved using your DI container.

Introduce two new interfaces instead and apply those to the repositories:

public interface ISqlProductRepository : IProductRepository
{
}

public interface IXmlProductRepository : IProductRepository
{
}

SQLProductRepository : ISqlProductRepository
XMLProductRepository : IXmlProductRepository

And register and inject those:

// Application startup
container.RegisterType<ISqlProductRepository, SQLProductRepository>();
container.RegisterType<IXmlProductRepository, XMLProductRepository>();

// Controller
private readonly ISqlProductRepository _sqlProductRepository;
private readonly IXmlProductRepository _xmlProductRepository;

public HomeController(ISqlProductRepository sqlProductRepository, IXmlProductRepository xmlProductRepository)
{
    _sqlProductRepository = sqlProductRepository;
    _xmlProductRepository = xmlProductRepository;
}

public ActionResult SqlMethod1()
{
    // use _sqlProductRepository
}

public ActionResult XmlMethod2()
{
    // use _xmlProductRepository
}

Of course now you can't inject XMLProductRepository for SQLProductRepository anymore, but that's a problem easily solved using mocking.

Anyway based on your current streak of questions, you're trying to learn something about unit testing and dependency injection. Please pick up a decent book and stop tying pieces together from blog posts, which hardly ever explain everything you need to know.

Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • can u give me idea when parameterize HomeController's Ctor will be called ? because when we call homecontroller by url then its parameter less ctor will be called. just tell in what kind of situation HomeController's parameterize ctor will be called ? does it happen only at unit testing? – Mou Sep 15 '15 at 08:38
  • @Mou again, please stop reading incomplete and outdated blog posts and asking uninformed questions based on those posts. Start over. Blog posts are not the way to thoroughly learn things; they are for when you partially know what you're doing. Buy a book or find a thorough tutorial by the makers of the DI framework that you use. Every tutorial for DI with MVC will explain you that the DI framework (Unity in this case) will register a controller factory that instantiates controllers that have parameterized constructors, or to set `DependencyResolver.SetResolver()`. – CodeCaster Sep 15 '15 at 08:58