StructureMap 在运行时根据已解析的类型覆盖依赖项
StructureMap override dependencies at runtime based on resolved type
在我的 MVC 应用程序中,我定义了两个接口:IQueryMappingsConfigurator
和 ICommandMappingsConfigurator
。这两个接口用于为查询和命令上下文提供 EntityFramework
映射。
在同一个解决方案中,我有两个服务:IMembershipService
和 IMessagingService
;对于这些服务中的每一个,都有一个 Registry
指定 ICommandMappingsConfigurator
和 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>();
}
}
每个服务都依赖于 IQueryMappingsConfigurator
和 ICommandMappingsConfigurator
。
IMembershipService
和IMessagingService
被MVC项目中的控制器使用:
public class MessageController : Controller
{
public MessageController(IMessagingService service){ }
}
public class MembershipController : Controller
{
public MembershipController(IMembershipService service){}
}
如何配置 StructureMap 容器,以便在需要 IMessagingService
的依赖项时,它将加载 ICommandMappingsConfigurator
和 IQueryMappingsConfigurator
的正确实现?
我试过使用这样的自定义注册约定:
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。除非你真的需要在你的应用程序中交换 MembershipCommandMappingsConfigurator
和 MessagingCommandMappingsConfigurator
的能力,一个简单的解决方案就是给每个不同的接口(在这种情况下 IMembershipCommandMappingsConfigurator
和 IMessagingCommandMappingsConfigurator
).
如果您没有违反 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>());
在我的 MVC 应用程序中,我定义了两个接口:IQueryMappingsConfigurator
和 ICommandMappingsConfigurator
。这两个接口用于为查询和命令上下文提供 EntityFramework
映射。
在同一个解决方案中,我有两个服务:IMembershipService
和 IMessagingService
;对于这些服务中的每一个,都有一个 Registry
指定 ICommandMappingsConfigurator
和 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>();
}
}
每个服务都依赖于 IQueryMappingsConfigurator
和 ICommandMappingsConfigurator
。
IMembershipService
和IMessagingService
被MVC项目中的控制器使用:
public class MessageController : Controller
{
public MessageController(IMessagingService service){ }
}
public class MembershipController : Controller
{
public MembershipController(IMembershipService service){}
}
如何配置 StructureMap 容器,以便在需要 IMessagingService
的依赖项时,它将加载 ICommandMappingsConfigurator
和 IQueryMappingsConfigurator
的正确实现?
我试过使用这样的自定义注册约定:
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。除非你真的需要在你的应用程序中交换 MembershipCommandMappingsConfigurator
和 MessagingCommandMappingsConfigurator
的能力,一个简单的解决方案就是给每个不同的接口(在这种情况下 IMembershipCommandMappingsConfigurator
和 IMessagingCommandMappingsConfigurator
).
如果您没有违反 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>());