没有注册默认实例,无法自动确定类型
No default Instance is registered and cannot be automatically determined for type
我的接口定义如下:
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"]; }
}
}
我还创建了一个工厂class来获取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
我正在为 MVC5 使用 StructureMap
我真的不能告诉你为什么你在 StructureMap 中注册失败,但如果你允许我,我想对你的设计提出反馈。
您的设计和代码违反了一些基本原则:
您违反了 Interface Segregation Princple (ISP)。
ISP 描述接口应该是窄的(角色接口)并且包含的成员不应超过消费者使用的数量。然而,您定义了一个应用程序范围的 IApplicationSettings
接口,您的意图是注入任何需要一些配置设置的消费者。变化非常小,但实际上有一个用户需要所有设置。这迫使消费者依赖所有成员,这使得 API 更复杂,而它只需要一个。
您违反了 Open/Closed Principle (OCP)。
OCP 描述应该可以在不更改代码库中现有 classes 的情况下添加新功能。但是,您会发现每次添加新设置时,您都会更新 IApplicationSettings
接口及其实现(您可能也会有一个 fake/mock 实现)。
启动时不读取配置值,这使得验证应用程序的配置变得更加困难。
当消费者调用您的 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
class,因为那样会再次重现同样的问题,在这种情况下,您实际上并没有进行抽象。相反,抽象使用此配置值的行为!对于连接字符串,创建允许创建 SqlConnection
或 DbContext
class 的 IConnectionFactory
或 IDbContextFactory
抽象。这完全隐藏了任何消费者都有连接字符串的事实,并允许他们调用 connectionFactory.CreateConnection()
而不是必须 fiddle 处理连接和连接字符串。
我的经验是使应用程序代码更简洁,并提高应用程序的可验证性。
您的代码无法运行的原因是,当您调用 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());
}
感谢大家的回复。我找到了我的解决方案。解决方案不是使用默认注册表,而是创建了另一个 class 来解决依赖关系。在 class 里面我用了
ObjectFactory.Initialize(x =>
{
x.AddRegistry<ControllerRegistry>();
});
而不是
IContainer Initialize() {
return new Container(c => c.AddRegistry<ControllerRegistry>());
}
然后在 ControllerRegistry 中,我按如下方式解决依赖关系:
// Application Settings
For<IApplicationSettings>().Use<WebConfigApplicationSettings>();
然后我在 Global.asax 中调用 class 如下:
Bootstrap.ConfigureDependencies();
最后在 Global.asax 中,我解决了工厂 class 的依赖关系,如下所示:
ApplicationSettingsFactory.InitializeApplicationSettingsFactory
(ObjectFactory.GetInstance<IApplicationSettings>());
我的全部代码如下:
Bootstrap class(新建)
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>());
我的接口定义如下:
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"]; }
}
}
我还创建了一个工厂class来获取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
我正在为 MVC5 使用 StructureMap
我真的不能告诉你为什么你在 StructureMap 中注册失败,但如果你允许我,我想对你的设计提出反馈。
您的设计和代码违反了一些基本原则:
您违反了 Interface Segregation Princple (ISP)。
ISP 描述接口应该是窄的(角色接口)并且包含的成员不应超过消费者使用的数量。然而,您定义了一个应用程序范围的
IApplicationSettings
接口,您的意图是注入任何需要一些配置设置的消费者。变化非常小,但实际上有一个用户需要所有设置。这迫使消费者依赖所有成员,这使得 API 更复杂,而它只需要一个。您违反了 Open/Closed Principle (OCP)。
OCP 描述应该可以在不更改代码库中现有 classes 的情况下添加新功能。但是,您会发现每次添加新设置时,您都会更新
IApplicationSettings
接口及其实现(您可能也会有一个 fake/mock 实现)。启动时不读取配置值,这使得验证应用程序的配置变得更加困难。
当消费者调用您的
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
class,因为那样会再次重现同样的问题,在这种情况下,您实际上并没有进行抽象。相反,抽象使用此配置值的行为!对于连接字符串,创建允许创建 SqlConnection
或 DbContext
class 的 IConnectionFactory
或 IDbContextFactory
抽象。这完全隐藏了任何消费者都有连接字符串的事实,并允许他们调用 connectionFactory.CreateConnection()
而不是必须 fiddle 处理连接和连接字符串。
我的经验是使应用程序代码更简洁,并提高应用程序的可验证性。
您的代码无法运行的原因是,当您调用 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());
}
感谢大家的回复。我找到了我的解决方案。解决方案不是使用默认注册表,而是创建了另一个 class 来解决依赖关系。在 class 里面我用了
ObjectFactory.Initialize(x =>
{
x.AddRegistry<ControllerRegistry>();
});
而不是
IContainer Initialize() {
return new Container(c => c.AddRegistry<ControllerRegistry>());
}
然后在 ControllerRegistry 中,我按如下方式解决依赖关系:
// Application Settings
For<IApplicationSettings>().Use<WebConfigApplicationSettings>();
然后我在 Global.asax 中调用 class 如下:
Bootstrap.ConfigureDependencies();
最后在 Global.asax 中,我解决了工厂 class 的依赖关系,如下所示:
ApplicationSettingsFactory.InitializeApplicationSettingsFactory
(ObjectFactory.GetInstance<IApplicationSettings>());
我的全部代码如下:
Bootstrap class(新建)
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>());