views:

174

answers:

3

We've been experimenting with StructureMap, and I'm having trouble grasping how to handle situations where a single interface has multiple implementations. The code below shows an example where we have two databases that are both accessible from a single service.

public class SomeController : Controller
{
 private ISomeService _service;
 private IClientRepository _repository;
 protected IContext _masterContext;
 protected IContext _clientContext;

 public SomeController(ISomeService service, ISomeRepository repository
  , IContext masterCon, IContext clientCon)
 {
  _service = service;
  _repository = repository;
  _masterContext = masterCon;
  _clientContext = clientCon;
 }
}

public class SomeService : ISomeService
{
 private IContext _masterContext;
 private IContext _clientContext;

 public SomeService(IContext masterContext, IContext clientContext)
 {
  masterContext = _masterContext;
  clientContext = _clientContext;
 }
}

public class ClientRepository : IClientRepository
{
 private IContext _clientContext;

 public ClientRepository(IContext clientContext)
 {
  _clientContext = clientContext;
 }
}

public class MasterContext : IContext
{
 public MasterContext(String connString)
 //<snip, snip> implement 3rd party data context
}

public class ClientContext : IContext
{
 public ClientContext(String connString)
 //<snip, snip> implement 3rd party data context
}

StructureMap worked GREAT when we had a single context (database), but how do I tell it how to resolve the 2nd? Note: in most situations we wouldn't have a service handling 2 databases (but may have a controller handling 2 connections, i.e. 2 repositories accessing 2 different databases), but it still doesn't seem to make it easier.

I'm half ready to just give up on using an IoC framework and go back to poor man's DI.

+2  A: 

Is it not possible to have an IClientContext and an IMasterContext, possibly inheriting from IContext. My feeling is that the code would be doing one of two very different things depending on whether you were talking to the 'Master' or 'Client' database.

Richard Nienaber
+1  A: 

In Unity you can have named registrations, allowing you to effectively register more than a class for a given interface. So you could do (typing by heart, check the actual Unity documentation if interested):

container.RegisterType<IContext, MasterContext>("Master");
container.RegisterType<IContext, ClientContext>("Client");

and then the constructor for SomeService would be:

public SomeService(
    [Dependency("Master")]IContext masterContext, 
    [Dependency("Client")]IContext clientContext)
{
    //...
}

The drawback is that in this way your service class is no longer independent of the DI framework used, but depending on the project that may be ok.

Konamiman
+1  A: 

This can be a little difficult if you're relying on StructureMap to resolve the dependencies automatically. The first solution (and what I'd err towards) is to make use of marker interfaces like Richard mentions in his answer then just register them. You can then explicitly specify whether you want your client or master context there.

The second way is to make use of named registrations, then specify the constructor params explicitly.

ForRequestedType<IContext>().AddInstances(
    i => {
        i.OfConcreteType<ClientContext>().WithName("Client");
        i.OfConcreteType<MasterContext>().WithName("Master");
    });
ForRequestedType<SomeController>().TheDefault.Is.ConstructedBy(
    i => new SomeController(i.GetInstance<ISomeService>(), 
        i.GetInstance<IClientRepository>(), 
        i.GetInstance<IContext>("Master"), 
        i.GetInstance<IContext>("Client")));

Not particularly nice but it does the job and ultimately if it's only in one or two places it might be OK.


If you want to resolve differently on namespace / assembly you could try something like this:-

ForRequestedType<IContext>().AddInstances(
    i => {
         i.OfConcreteType<ClientContext>().WithName("Client");
         i.OfConcreteType<MasterContext>().WithName("Master");
     }).TheDefault.Is.Conditional(c => {
         c.If(con => con.ParentType.Namespace.EndsWith("Client"))
          .ThenIt.Is.TheInstanceNamed("Client");
         c.If(con => con.ParentType.Namespace.EndsWith("Master"))
          .ThenIt.Is.TheInstanceNamed("Master");
         c.TheDefault.Is.OfConcreteType<ClientContext>();
     });

Where the predicate on ParentType can refer to Assembly (or whatever you want really)

sighohwell
We actually use these all over the place (dozens of controllers). At lower levels, we could separate it so that only 1 context is used per project (we have separate Client and Master projects) - but I didn't see how I could set StructureMap to resolve differently by project/assembly
Jess
Updated regarding resolving differently by Assembly
sighohwell