调用GetInstance方法后,实例的简单注入器注册

时间:2017-01-11 07:01:28

标签: c# asp.net asp.net-web-api dependency-injection simple-injector

我有一个场景,我有一个webAPi客户端,它需要在其构造函数中使用基本URL,以及一个从web.config读取配置的配置管理器类。

interface IConfigManager
{
   string baseurl {get;}
}
class ConfigManager:IConfigManager
{
 public string baseurl => system.configuration.ConfigruationManager.AppSettings["URL"];
}

我有一个调用web api的客户端类

interface IApiClient
{
 List<Products> GetProducts(string addresssufix);
}

public class ApiClient:IApiClient
{
public ApiClient(string baseUrl)
{
//----
}
 List<Products> GetProducts(string addresssufix)
{
//....
}
}

所以我需要在APiClient中使用Url

在简单的进样器中注册组件。

 container.Register<IConfigManager, ConfigManager>();
var config= container.GetInstance<IConfigManager>();

container.Register<IApiClient<(()=> new ApiClient(config.baseurl));

但它说我在调用GetInstance后无法注册

2 个答案:

答案 0 :(得分:3)

Simple Injector在第一次调用Register后阻止对GetInstance的任何调用,强制注册和解析之间进行严格注册。此设计可防止出现奇怪,难以调试且难以验证的行为,如文档中更详细的说明here

但正如您想要将注册阶段与开始从容器中解析的阶段分开一样,您应该在读取配置值时执行相同操作。配置值应仅在应用程序启动时,在注册阶段之前或期间加载。延迟读取配置值会导致应用程序变得脆弱,并迫使您通过整个应用程序来确定应用程序是否配置正确,而这可以通过在启动时加载配置来轻松防止(从而让应用程序快速失败)。

这意味着,在您的情况下,拥有IConfigManager抽象没有多大意义,因为它的唯一目标是延迟从应用程序设置加载基本URL,同样,这应该是在启动时直接完成(如果值缺失或格式错误,最好应该失败)。

考虑到这一点,我想对您的设计提出以下改进和简化:

var container = new Container();

string baseUrl = System.Configuration.ConfigruationManager.AppSettings["URL"];

if (string.IsNullOrEmpty(baseUrl))
    throw new ConfigurationErrorsException("appSettings/URL is missing.");

container.RegisterSingleton<IApiClient>(new ApiClient(baseUrl));

查看在启动时如何直接读取配置并立即检查该值是否存在。之后,baseUrl直接在ApiClient构造函数中使用。另请注意,ApiClient已注册为Singleton。我在这里假设ApiClient是无状态且不可变的(这是一种很好的做法)。

请注意,您通过让ApiClient依赖于string baseUrl配置值而不是注入IConfigManager来做正确的事情。使用ConfigManager作为应用程序代码中的抽象通常是有问题的。这种配置抽象通常会在应用程序的生命周期内无限增长,因为每次将新配置值添加到配置时,都需要更新此抽象(及其实现和可能的伪实现)。该抽象的消费者通常仅依赖于一个或几个配置值,但从不依赖于所有配置值。这表示Interface Segregation Principle违规行为。问题在于,测试该接口的使用者变得更加困难,因为您通常希望确保他们使用正确的配置值。另一个问题是,根据这种消费者的定义(其类型名称及其具有所需依赖性的构造函数),无法确定实际需要哪些配置值。

当您让消费者直接依赖他们所需的配置值时,所有这些问题都会完全消失。但同样,这甚至消除了首先进行IConfigManager抽象的需要。

请注意,虽然不允许使用register-resolve-register,但您可以执行以下操作:

container.Register<IConfigManager, ConfigManager>();

container.Register<IApiClient>(() =>
    new ApiClient(container.GetInstance<IConfigManager>().baseurl));

这里发生的事情是GetInstance<IConfigManager>被称为IApiClient代表的一部分。这将有效,因为在这种情况下,GetInstance<IConfigManager>()在解析IApiClient时被调用,因此在注册过程之后被调用。换句话说,解决IConfigManager会延迟。

虽然对此有很大的警告:通常不建议采用这种做法。如前所述,在配置值方面,我们不想懒得加载它们。但即使在其他情况下,我们通常也不希望这样做,因为这种结构会使Simple Injector的验证和诊断系统失效。由于Simple Injector使用静态可用信息(例如构造函数参数)来分析对象图,因此这种动态调用将禁止Simple Injector查找常见问题,例如Lifestyle Mismatches。换句话说,只有在您确定不会发生错误配置的情况下,才应该在极少数情况下使用此构造。

答案 1 :(得分:1)

将依赖对象传递给ApiClient,而不仅仅是它的属性。如果该对象具有太多对ApiClient不感兴趣的属性,请进行接口隔离。

container.Register<IConfigManager, ConfigManager>();
container.Register<IApiClient, ApiClient>();

public ApiClient(IConfigManager configManager)
{
   this.baseurl = configManager.baseurl;
}

container.GetInstance<ApiClient>();