Reputation: 9576
In my MVC application I defined two interfaces: IQueryMappingsConfigurator
and ICommandMappingsConfigurator
. Those two interfaces are used for supplying EntityFramework
mappings for query and command contexts.
In the same solution I have two services: IMembershipService
and IMessagingService
; for each of those services there is a Registry
specifying implementations for ICommandMappingsConfigurator
and IQueryMappingsConfigurator
:
// In Services.Membership project
public class MembershipRegistry : Registry
{
public MembershipRegistry()
{
For<ICommandMappingsConfigurator>()
.Use<MembershipCommandMappingsConfigurator>();
For<IQueryMappingsConfigurator>()
.Use<MembershipQueryMappingsConfigurator>();
For<IMembershipService>()
.Use<MembershipService>();
}
}
// In Services.Messaging project
public class MessagingRegistry : Registry
{
public MessagingRegistry()
{
For<ICommandMappingsConfigurator>()
.Use<MessagingCommandMappingsConfigurator>();
For<IQueryMappingsConfigurator>()
.Use<MessagingQueryMappingsConfigurator>();
For<IMessagingService>()
.Use<MessagingService>();
}
}
Each service has a dependency on both IQueryMappingsConfigurator
and ICommandMappingsConfigurator
.
IMembershipService
and IMessagingService
are used by controllers in the MVC project:
public class MessageController : Controller
{
public MessageController(IMessagingService service){ }
}
public class MembershipController : Controller
{
public MembershipController(IMembershipService service){}
}
How can I configure the StructureMap container so that when a dependency for IMessagingService
is required it will load the proper implementation for ICommandMappingsConfigurator
and IQueryMappingsConfigurator
?
I've tried using a custom registration convention like this:
public class ServiceRegistrationConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
if (IsApplicationService(type))
{
registry.Scan(_ =>
{
_.AssemblyContainingType(type);
_.LookForRegistries();
});
}
}
}
However, when I try accessing an action method from MessageController
I get the error
There is no configuration specified for IMessagingService
When I debug the application I can see the Process
method being hit with the type of IMessagingService
.
Upvotes: 1
Views: 3977
Reputation: 56909
The correct way to compose your application is to make a composition root. If you indeed have one (which isn't clear from your question), this step is done only 1 time per application start so there is no way to change the DI configuration's state during runtime (or at least you should assume there is not). It doesn't matter if the dependencies are in different application layers, they will overlap if they use the same interface.
Before you change your DI configuration, you should check whether you are violating the Liskov Substitution Principle. Unless you really need the ability to swap the MembershipCommandMappingsConfigurator
and MessagingCommandMappingsConfigurator
in your application, a simple solution is just to give each a different interface (in this case IMembershipCommandMappingsConfigurator
and IMessagingCommandMappingsConfigurator
).
If you are not violating the LSP, one option is to use generics to disambiguate the dependency chain.
public class MyRegistry : Registry
{
public MyRegistry()
{
For(typeof(ICommandMappingsConfigurator<>))
.Use(typeof(CommandMappingsConfigurator<>));
For(typeof(IQueryMappingsConfigurator<>)
.Use(typeof(QueryMappingsConfigurator<>));
For<IMessagingService>()
.Use<MessagingService>();
For<IMembershipService>()
.Use<MembershipService>();
}
}
public class CommandMappingsConfigurator<MessagingService> : ICommandMappingsConfigurator<MessagingService>
{
// ...
}
public class QueryMappingsConfigurator<MessagingService> : IQueryMappingsConfigurator<MessagingService>
{
// ...
}
public class MessagingService
{
public MessagingService(
ICommandMappingsConfigurator<MessagingService> commandMappingsConfigurator,
IQueryMappingsConfigurator<MessagingService> queryMappingsConfigurator)
{
// ...
}
}
public class CommandMappingsConfigurator<MembershipService> : ICommandMappingsConfigurator<MembershipService>
{
// ...
}
public class QueryMappingsConfigurator<MembershipService> : IQueryMappingsConfigurator<MembershipService>
{
// ...
}
public class MembershipService
{
public MembershipService(
ICommandMappingsConfigurator<MembershipService> commandMappingsConfigurator,
IQueryMappingsConfigurator<MembershipService> queryMappingsConfigurator)
{
// ...
}
}
Another option - in StructureMap you can used smart instances in the configuration to specify exactly what instance goes where, so at runtime you can have different implementations of the same interface.
public class MembershipRegistry : Registry
{
public MembershipRegistry()
{
var commandMappingsConfigurator = For<ICommandMappingsConfigurator>()
.Use<MembershipCommandMappingsConfigurator>();
var queryMappingsConfigurator = For<IQueryMappingsConfigurator>()
.Use<MembershipQueryMappingsConfigurator>();
For<IMembershipService>()
.Use<MembershipService>()
.Ctor<ICommandMappingsConfigurator>().Is(commandMappingsConfigurator)
.Ctor<IQueryMappingsConfigurator>().Is(queryMappingsConfigurator);
}
}
public class MessagingRegistry : Registry
{
public MessagingRegistry()
{
var commandMappingsConfigurator = For<ICommandMappingsConfigurator>()
.Use<MessagingCommandMappingsConfigurator>();
var queryMappingsConfigurator = For<IQueryMappingsConfigurator>()
.Use<MessagingQueryMappingsConfigurator>();
For<IMessagingService>()
.Use<MessagingService>();
.Ctor<ICommandMappingsConfigurator>().Is(commandMappingsConfigurator)
.Ctor<IQueryMappingsConfigurator>().Is(queryMappingsConfigurator);
}
}
You can also use named instances, but smart instances have compile-time type checking support which makes them easier to configure.
There is no reason to use .Scan
(which uses Reflection) to configure the registries, unless your application has some kind of plugin architecture. For a normal application with multiple layers, you can configure them explicitly.
var container = new Container();
container.Configure(r => r.AddRegistry<MembershipRegistry>());
container.Configure(r => r.AddRegistry<MessagingRegistry>());
Upvotes: 1