StructureMap 在运行时根据已解析的类型覆盖依赖项

StructureMap override dependencies at runtime based on resolved type

在我的 MVC 应用程序中,我定义了两个接口:IQueryMappingsConfiguratorICommandMappingsConfigurator。这两个接口用于为查询和命令上下文提供 EntityFramework 映射。

在同一个解决方案中,我有两个服务:IMembershipServiceIMessagingService;对于这些服务中的每一个,都有一个 Registry 指定 ICommandMappingsConfiguratorIQueryMappingsConfigurator:

的实现
// 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>();
    }
}

每个服务都依赖于 IQueryMappingsConfiguratorICommandMappingsConfigurator

IMembershipServiceIMessagingService被MVC项目中的控制器使用:

public class MessageController : Controller
{
    public MessageController(IMessagingService service){ }
}

public class MembershipController : Controller
{
    public MembershipController(IMembershipService service){}
}

如何配置 StructureMap 容器,以便在需要 IMessagingService 的依赖项时,它将加载 ICommandMappingsConfiguratorIQueryMappingsConfigurator 的正确实现?

我试过使用这样的自定义注册约定:

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        if (IsApplicationService(type))
        {
            registry.Scan(_ =>
            {
                _.AssemblyContainingType(type);
                _.LookForRegistries();
            });
        }
    }
}

但是,当我尝试从 MessageController 访问操作方法时,出现错误

There is no configuration specified for IMessagingService

当我调试应用程序时,我可以看到 Process 方法被命中,类型为 IMessagingService

编写应用程序的正确方法是制作 composition root。如果您确实有一个(从您的问题中不清楚),则此步骤在每次应用程序启动时仅执行 1 次,因此无法在运行时更改 DI 配置的状态(或者至少您应该假设没有) .依赖在不同的应用层没有关系,如果它们使用相同的接口,它们就会重叠。

在更改 DI 配置之前,您应该检查是否违反了 Liskov Substitution Principle。除非你真的需要在你的应用程序中交换 MembershipCommandMappingsConfiguratorMessagingCommandMappingsConfigurator 的能力,一个简单的解决方案就是给每个不同的接口(在这种情况下 IMembershipCommandMappingsConfiguratorIMessagingCommandMappingsConfigurator ).

如果您没有违反 LSP,一种选择是使用泛型来消除依赖链的歧义。

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)
    {
        // ...
    }
}

另一种选择 - 在 StructureMap 中,您可以在配置中使用智能实例来准确指定实例的位置,因此在运行时您可以拥有同一接口的不同实现。

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

您也可以使用命名实例,但智能实例具有编译时类型检查支持,这使得它们更易于配置。

没有理由使用 .Scan(使用反射)来配置注册表,除非您的应用程序具有某种插件架构。对于具有多层的普通应用程序,您可以显式配置它们。

var container = new Container();

container.Configure(r => r.AddRegistry<MembershipRegistry>());
container.Configure(r => r.AddRegistry<MessagingRegistry>());