我有一个场景,我有一个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后无法注册
答案 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>();