【问题标题】:StructureMap override dependencies at runtime based on resolved typeStructureMap 在运行时根据解析的类型覆盖依赖项
【发布时间】:2015-03-07 22:26:18
【问题描述】:

在我的 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 访问操作方法时,我得到了错误

没有为 IMessagingService 指定配置

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

【问题讨论】:

  • IMessageService 真的没有 DI 配置(这可以解释你的错误),还是你不小心把它排除在问题之外?如果不是,则很难准确判断问题出在哪里,因为您只发布了部分配置,其中一些界面似乎不相关。
  • @NightOwl888,IMessagingService 的 DI 配置在注册表中指定。请查看编辑。

标签: c# asp.net-mvc dependency-injection structuremap


【解决方案1】:

编写应用程序的正确方法是创建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>());

【讨论】:

  • 我决定采用泛型方法。谢谢@NightOwl888。
猜你喜欢
  • 2014-03-18
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 2021-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多