我的界面定义如下:
public interface IApplicationSettings
{
string LoggerName { get; }
string NumberOfResultsPerPage { get; }
string EmailAddress { get; }
string Credential { get; }
}
此界面的实现如下:
public class WebConfigApplicationSettings : IApplicationSettings
{
public string LoggerName
{
get { return ConfigurationManager.AppSettings["LoggerName"]; }
}
public string NumberOfResultsPerPage
{
get { return ConfigurationManager.AppSettings["NumberOfResultsPerPage"]; }
}
public string EmailAddress
{
get { return ConfigurationManager.AppSettings["EmailAddress"]; }
}
public string Credential
{
get { return ConfigurationManager.AppSettings["Credential"]; }
}
}
我还创建了一个工厂类来获取WebConfigSettings具体实现的实例,如下所示:
public class ApplicationSettingsFactory
{
private static IApplicationSettings _applicationSettings;
public static void InitializeApplicationSettingsFactory(
IApplicationSettings applicationSettings)
{
_applicationSettings = applicationSettings;
}
public static IApplicationSettings GetApplicationSettings()
{
return _applicationSettings;
}
}
然后我按如下方式解决了依赖:
public class DefaultRegistry : Registry {
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
For<IApplicationSettings>().Use<WebConfigApplicationSettings>();
ApplicationSettingsFactory.InitializeApplicationSettingsFactory
(ObjectFactory.GetInstance<IApplicationSettings>());
}
}
现在,当我运行我的应用程序时,它会引发异常:
Exception has been thrown by the target of an invocation.
和内部例外是
No default Instance is registered and cannot be automatically determined for type 'Shoppingcart.Infrastructure.Configuration.IApplicationSettings'\r\n\r\nThere is no configuration specified for Shoppingcart.Infrastructure.Configuration.IApplicationSettings\r\n\r\n1.) Container.GetInstance(Shoppingcart.Infrastructure.Configuration.IApplicationSettings)\r\n
我正在使用StructureMap for MVC5
答案 0 :(得分:6)
您的代码无法正常工作的原因是,当您致电ObjectFactory.GetInstance<IApplicationSettings>()
时,您的注册表尚未注册,因此,StructureMap的配置不完整。
我相信你要做的是以下(测试和工作):
public class ApplicationSettingsFactory
{
public ApplicationSettingsFactory(WebConfigApplicationSettings applicationSettings)
{
_applicationSettings = applicationSettings;
}
private static IApplicationSettings _applicationSettings;
public IApplicationSettings GetApplicationSettings()
{
return _applicationSettings;
}
}
您的注册表配置如下:
public DefaultRegistry() {
Scan(scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
this.For<IApplicationSettings>().Use(ctx => ctx.GetInstance<ApplicationSettingsFactory>().GetApplicationSettings());
}
答案 1 :(得分:4)
感谢每一位回复者。我找到了解决方案。解决方案是使用Default Registry而不是使用Default Registry我创建了另一个类来解决依赖关系。在课堂上我用了
ObjectFactory.Initialize(x =>
{
x.AddRegistry<ControllerRegistry>();
});
而不是
IContainer Initialize() {
return new Container(c => c.AddRegistry<ControllerRegistry>());
}
然后在ControllerRegistry内部我解决了依赖关系,如下所示:
// Application Settings
For<IApplicationSettings>().Use<WebConfigApplicationSettings>();
然后我在 Global.asax 中调用了该类,如下所示:
Bootstrap.ConfigureDependencies();
最后在 Global.asax 内,我解决了Factory类的依赖关系,如下所示:
ApplicationSettingsFactory.InitializeApplicationSettingsFactory
(ObjectFactory.GetInstance<IApplicationSettings>());
我的整个代码如下:
Bootstrap类(新创建)
public class Bootstrap
{
public static void ConfigureDependencies()
{
ObjectFactory.Initialize(x =>
{
x.AddRegistry<ControllerRegistry>();
});
}
public class ControllerRegistry : Registry
{
public ControllerRegistry()
{
// Application Settings
For<IApplicationSettings>().Use<WebConfigApplicationSettings>();
}
}
}
Global.asax
Bootstrap.ConfigureDependencies();
ApplicationSettingsFactory.InitializeApplicationSettingsFactory
(ObjectFactory.GetInstance<IApplicationSettings>());
答案 2 :(得分:2)
我无法告诉您为什么您的注册在StructureMap中失败,但如果您允许我,我想反馈您的设计。
您的设计和代码违反了一些基本原则:
您违反了Interface Segregation Princple(ISP)。
ISP描述接口应该是狭窄的(角色接口),并且不应包含比消费者使用的更多的成员。但是,您定义了一个应用程序范围IApplicationSettings
接口,您的意图是注入需要某些配置设置的任何消费者。变化确实很小但是有一个消费者实际上需要所有设置。这迫使消费者依赖所有成员,它使API变得更加复杂,而它只需要一个。
您违反了Open/Closed Principle(OCP)。
OCP描述了应该可以添加新功能而无需更改代码库中的现有类。然而,每次添加新设置时,您都会发现自己正在更新IApplicationSettings
接口及其实现(您可能也会有假/模拟实现)。
启动时无法读取配置值,这使得验证应用程序配置变得更加困难。
当消费者调用IApplicationSettings
抽象的属性时,您将呼叫转发到ConfigurationManager.AppSettings
。这意味着如果值不可用或格式不正确,应用程序将在运行时失败。由于某些配置值仅在某些情况下使用,因此强制您在部署应用程序后测试每个此类情况,以确定系统是否配置正确。
<强>解决方案强>
解决这些问题实际上非常简单:
在启动时直接加载配置值,允许应用程序在发生配置错误时快速失败,并防止配置被不必要地一遍又一遍地重复读取。
将配置值直接注入组件,可防止该组件依赖于不断变化的界面。它非常清楚组件所依赖的内容,并在应用程序启动期间烘焙此信息。
这并不意味着您无法使用某种ApplicationSettings
DTO。这样的DTO正是我在我的应用程序中使用的。这基本上如下:
public static Container Bootstrap() {
return Bootstrap(new ApplicationSettings
{
LoggerName = ConfigurationManager.AppSettings["LoggerName"],
NumberOfResultsPerPage = int.Parse(
ConfigurationManager.AppSettings["NumberOfResultsPerPage"]),
EmailAddress = new MailAddres(
ConfigurationManager.AppSettings["EmailAddress"]),
Credential = ConfigurationManager.AppSettings["Credential"],
});
}
public static Container Bootstrap(ApplicationSettings settings) {
var container = new Container();
container.RegisterSingle<ILogger>(
new SmtpLogger(settings.LoggerName, settings.EmailAddress));
container.RegisterSingle<IPagingProvider>(
new PagingProvider(settings.NumberOfResultsPerPage));
// Etc
return container;
}
在上面的代码中,您将看到ApplicationSettings
DTO的创建与容器的配置分开。这样我就可以在集成测试中测试我的DI配置,其中启动项目配置文件不可用。
另请注意,我将配置值直接提供给需要它的组件的构造函数。
您可能会持怀疑态度,因为它可能会污染您的DI配置,因为您有许多需要使用相同配置值设置的对象。例如,您的应用程序可能有许多存储库,每个存储库都需要一个连接字符串。
但我的经验是,你有很多组件需要相同的配置值;你错过了抽象。但是不要创建一个IConnectionStringSettings
类,因为这会再次重现同样的问题,在这种情况下,你并没有真正做出抽象。相反,抽象使用此配置值的行为!对于连接字符串,请创建IConnectionFactory
或IDbContextFactory
抽象,以允许创建SqlConnection
或DbContext
类。这完全隐藏了这样一个事实,即任何消费者都有一个连接字符串,并允许他们调用connectionFactory.CreateConnection()
,而不必乱用连接和连接字符串。
我的经验是使应用程序代码更清晰,并提高应用程序的可验证性。