如果您的包包含一个配置类IServiceCollection
,比如这样:
public class MyPackageInstaller
{
public void Install(IServiceCollection services)
{
// Your package registers its services
}
}
那么这也可以是您允许消费者进行可选更改的点。例如,您可以定义一个这样的类,它允许使用者指定某些服务的实现:
public class MyPackageRegistrationOptions
{
public ServiceDescriptor FooServiceDescriptor { get; private set; }
public void AddFooService(ServiceDescriptor fooDescriptor)
{
if (fooDescriptor.ServiceType != typeof(IFooService))
{
throw new ArgumentException("fooDescriptor must register type IFooService.");
}
FooServiceDescriptor = fooDescriptor;
}
}
现在,您的安装程序可以采用这些选项,并注册消费者指定的实现或其自己的默认实现。
public class MyPackageInstaller
{
private readonly MyPackageRegistrationOptions _options;
public MyPackageInstaller(MyPackageRegistrationOptions options = null)
{
_options = options;
}
public void Install(IServiceCollection services)
{
if (_options?.FooServiceDescriptor != null)
services.Add(_options.FooServiceDescriptor);
else
// here's your default implementation
services.AddSingleton<FooService>();
}
}
Usage:
var services = new ServiceCollection();
var options = new MyPackageRegistrationOptions();
options.AddFooService(ServiceDescriptor.Singleton<IFooService, AlternateFooService>());
var installer = new MyPackageInstaller(options);
installer.Install(services);
乍一看,获得相同结果的方法似乎更长。好处是它可以让您更清楚哪些服务应该或不应该被覆盖。这样,感觉更像是在使用故意暴露的配置选项,而不是在探究包的内部结构。
而不是允许消费者添加ServiceDescriptor
您可以允许他们仅指定一种服务类型,并且您的配置决定它如何注册(单例、瞬态等)
当库依赖于配置值(例如必须由使用者提供的连接字符串)时,这也是一种有用的模式。您可以将它们设置为构建选项的必需参数,然后将它们设置为构建安装程序所需的选项,或者仅将它们设置为安装程序中的必需参数。现在,如果没有所需的配置值,就无法安装该软件包。