简体   繁体   中英

StructureMap override dependencies at runtime based on resolved type

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 .

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>());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM