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

时间:2015-03-07 22:26:18

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

在我的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

MVC项目中的控制器使用

IMembershipServiceIMessagingService

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方法。

1 个答案:

答案 0 :(得分:1)

撰写应用程序的正确方法是制作composition root。如果你确实有一个(你的问题没有清楚),这个步骤每个应用程序启动只执行一次,所以在运行时没有办法改变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(使用Reflection)来配置注册表。对于具有多个图层的普通应用程序,您可以显式配置它们。

var container = new Container();

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