Beep beep
Beep beep

Reputation: 19141

Handling dependencies with IoC that change within a single function call

We are trying to figure out how to setup Dependency Injection for situations where service classes can have different dependencies based on how they are used. In our specific case, we have a web app where 95% of the time the connection string is the same for the entire Request (this is a web application), but sometimes it can change.

For example, we might have 2 classes with the following dependencies (simplified version - service actually has 4 dependencies):

public LoginService (IUserRepository userRep)
{
}

public UserRepository (IContext dbContext)
{
}

In our IoC container, most of our dependencies are auto-wired except the Context for which I have something like this (not actual code, it's from memory ... this is StructureMap): x.ForRequestedType().Use() .WithCtorArg("connectionString").EqualTo(Session["ConnString"]);

For 95% of our web application, this works perfectly. However, we have some admin-type functions that must operate across thousands of databases (one per client). Basically, we'd want to do this:

public CreateUserList(IList<string> connStrings)
{
   foreach (connString in connStrings)
   {
       //first create dependency graph using new connection string 
       ????       
       //then call service method on new database
       _loginService.GetReportDataForAllUsers();
   }
}

My question is: How do we create that new dependency graph for each time through the loop, while maintaining something that can easily be tested?

Upvotes: 2

Views: 196

Answers (4)

Beep beep
Beep beep

Reputation: 19141

We ended up just creating a concrete context and injecting that, then changing creating a wrapper class that changed the context's connection string. Seemed to work fine.

Upvotes: 0

Jeff Sternal
Jeff Sternal

Reputation: 48583

So most UserRepository methods use a single connection string obtained from session, but several methods need to operate against a list of connection strings?

You can solve this problem by promoting the connection string dependency from IContext to the repository and adding two additional dependencies - a context factory and a list of all the possible connections strings the repository might need to do its work:

public UserRepository(IContextFactory contextFactory, 
                      string          defaultConnectionString, 
                      List<string>    allConnectionStrings)

Then each of its methods can build as many IContext instances as they need:

// In UserRepository

public CreateUserList() {
    foreach (string connString in allConnectionStrings) {
        IContext context = contextFactory.CreateInstance(connString);
        // Build the rest of the dependency graph, etc. 
        _loginService.GetReportDataForAllUsers();
    }
}

public LoginUser() {
    IContext context = contextFactory.CreateInstance(defaultConnectionString);
    // Build the rest of the dependency graph, etc.
}

Upvotes: 0

Joshua Flanagan
Joshua Flanagan

Reputation: 8557

Within the loop, do:

container.With("connectionString").EqualTo(connString).GetInstance<ILoginService>()

where "connectionString" is the name of a string constructor parameter on the concrete implementation of ILoginService.

Upvotes: 0

Bryan Watts
Bryan Watts

Reputation: 45435

To defer the creation of an object until runtime, you can use a factory:

public interface ILoginServiceFactory
{
    ILoginService CreateLoginService(string connectionString);
}

Usage:

public void CreateUserList(IList<string> connStrings)
{
    foreach(connString in connStrings)
    {
        var loginService = _loginServiceFactory.CreateLoginService(connString);

        loginService.GetReportDataForAllUsers();
    }
}

Upvotes: 2

Related Questions