重新整理 .net core 实践篇—————服务与配置之间[十一二]

前言

前面基本介绍了,官方对于asp .net core 设计配置和设计服务的框架的一些思路。看下服务和配置之间是如何联系的吧。

正文

服务:

public interface ISelfService
{
	string ShowOptionName();
}

public class SelfService : ISelfService
{
	IOptions<SelfServiceOption> _options;
	public SelfService(IOptions<SelfServiceOption> options)
	{
		this._options = options;
	}
	public string ShowOptionName()
	{
		return _options.Value.Name;
	}
}

实体配置类:

public class SelfServiceOption
{
	public string Name { get; set; }
}

配置:

"SelfService": {
"name" : "zhangsan" 
}

注册:

services.AddSingleton<ISelfService, SelfService>();
services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"));

获取调用在startup.Configure中:

var SelfService = app.ApplicationServices.GetService<ISelfService>();

Console.WriteLine(SelfService.ShowOptionName());

结果:

经过前面系列中,我们非常好的能够理解:services.Configure(Configuration.GetSection("SelfService"));

在反射通过属性获取值过程中就是SelfService+":"+属性名,对字典进行获取对应的值。

那么看下为什么我们在配置的时候需要IOptions options,也就是套一个Ioption呢?Ioption它是怎么实现的呢?它的机制是什么?

看下IOptions 的实现类OptionsManager:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
	private readonly IOptionsFactory<TOptions> _factory;
	private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="factory">The factory to use to create options.</param>
	public OptionsManager(IOptionsFactory<TOptions> factory)
	{
		_factory = factory;
	}

	/// <summary>
	/// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
	/// </summary>
	public TOptions Value
	{
		get
		{
			return Get(Options.DefaultName);
		}
	}

	/// <summary>
	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
	/// </summary>
	public virtual TOptions Get(string name)
	{
		name = name ?? Options.DefaultName;

		// Store the options in our instance cache
		return _cache.GetOrAdd(name, () => _factory.Create(name));
	}
}

那么我们调用Value 其实是调用IOptionsFactory的_factory.Create(name)。

查看一下,IOptionsFactory的实现类OptionsFactory,看里面的create方法。:

public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
	private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
	private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
	private readonly IEnumerable<IValidateOptions<TOptions>> _validations;

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="setups">The configuration actions to run.</param>
	/// <param name="postConfigures">The initialization actions to run.</param>
	public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
	{ }

	/// <summary>
	/// Initializes a new instance with the specified options configurations.
	/// </summary>
	/// <param name="setups">The configuration actions to run.</param>
	/// <param name="postConfigures">The initialization actions to run.</param>
	/// <param name="validations">The validations to run.</param>
	public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
	{
		_setups = setups;
		_postConfigures = postConfigures;
		_validations = validations;
	}

	/// <summary>
	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
	/// </summary>
	public TOptions Create(string name)
	{
		var options = new TOptions();
		foreach (var setup in _setups)
		{
			if (setup is IConfigureNamedOptions<TOptions> namedSetup)
			{
				namedSetup.Configure(name, options);
			}
			else if (name == Options.DefaultName)
			{
				setup.Configure(options);
			}
		}
		foreach (var post in _postConfigures)
		{
			post.PostConfigure(name, options);
		}

		if (_validations != null)
		{
			var failures = new List<string>();
			foreach (var validate in _validations)
			{
				var result = validate.Validate(name, options);
				if (result.Failed)
				{
					failures.AddRange(result.Failures);
				}
			}
			if (failures.Count > 0)
			{
				throw new OptionsValidationException(name, typeof(TOptions), failures);
			}
		}

		return options;
	}	
}

切开三段看:

第一段

var options = new TOptions();
foreach (var setup in _setups)
{
	if (setup is IConfigureNamedOptions<TOptions> namedSetup)
	{
		namedSetup.Configure(name, options);
	}
	else if (name == Options.DefaultName)
	{
		setup.Configure(options);
	}
}

上面是实例化我们的配置类。

namedSetup.Configure(name, options); 就是给我们selfServiceOption具体绑定。

这就要回到我们注入的地方了。

services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"));

来看下Configure 写的是什么,自己看具体的实现哈:

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
	where TOptions : class
{
	if (services == null)
	{
		throw new ArgumentNullException(nameof(services));
	}

	if (config == null)
	{
		throw new ArgumentNullException(nameof(config));
	}

	services.AddOptions();
	services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
	return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}

AddOptions方法 就是注册具体实现的。

public static IServiceCollection AddOptions(this IServiceCollection services)
{
	if (services == null)
	{
		throw new ArgumentNullException(nameof(services));
	}

	services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
	services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
	services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
	services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
	services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
	return services;
}

重点来看下NamedConfigureFromConfigurationOptions 这个东西:

public class NamedConfigureFromConfigurationOptions<TOptions> : ConfigureNamedOptions<TOptions>
	where TOptions : class
{
	/// <summary>
	/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
	/// </summary>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
	public NamedConfigureFromConfigurationOptions(string name, IConfiguration config)
		: this(name, config, _ => { })
	{ }

	/// <summary>
	/// Constructor that takes the <see cref="IConfiguration"/> instance to bind against.
	/// </summary>
	/// <param name="name">The name of the options instance.</param>
	/// <param name="config">The <see cref="IConfiguration"/> instance.</param>
	/// <param name="configureBinder">Used to configure the <see cref="BinderOptions"/>.</param>
	public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
		: base(name, options => config.Bind(options, configureBinder))
	{
		if (config == null)
		{
			throw new ArgumentNullException(nameof(config));
		}
	}
}

NamedConfigureFromConfigurationOptions 继承ConfigureNamedOptions是不是很眼熟了。

看下我们第一段调用部分:

if (setup is IConfigureNamedOptions<TOptions> namedSetup)
{
	namedSetup.Configure(name, options);
}

后面就会调用NamedConfigureFromConfigurationOptions的Configure。

看下NamedConfigureFromConfigurationOptions的实例化方法,传递方法的第二个参数,options => config.Bind(options, configureBinder);

这个bind 是不是特别的眼熟,就是用来实体类绑定配置的,如有疑问可以看下前面几篇,讲述了绑定过程。

那么看下ConfigureNamedOptions的Configure(NamedConfigureFromConfigurationOptions继承ConfigureNamedOptions) 做了什么吧,我们来到ConfigureNamedOptions:

public Action<TOptions> Action { get; }

public ConfigureNamedOptions(string name, Action<TOptions> action)
{
	Name = name;
	Action = action;
}

public virtual void Configure(string name, TOptions options)
{
	if (options == null)
	{
		throw new ArgumentNullException(nameof(options));
	}

	// Null name is used to configure all named options.
	if (Name == null || name == Name)
	{
		Action?.Invoke(options, Dependency);
	}
}

这时候就会调用传递进来的Action:options => config.Bind(options, configureBinder),传递的这个options就是我们实例化的SelfServiceOption。

configureBinder 是一个配置哈,这是个配置的,下面第二个参数,如果没穿默认是一个()=>{};

如果需要传递可以这么写:

services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
{
	BinderOptions.BindNonPublicProperties = true;
});

是不是很眼熟,就是设置可以绑定私有属性的选项。

这就把我们的SelfServiceOption 和 Configuration.GetSection("SelfService") 已经绑定了。

那么来看第二段:

foreach (var post in _postConfigures)
{
	post.PostConfigure(name, options);
}

这个是什么呢? 这个是这样的,比如说我们的现在获取到了配置,得到了实体类SelfServiceOption,里面的name 是zhangsan。

这个PostConfigure 可以帮我们做后续修改,比如说我获取到了是zhangsan,但是呢,我想做一个判断,如果name 是zhangsan,就加一个后缀,name= name+"_a";

大概就是一些后续操作。

services.AddOptions<SelfServiceOption>().Configure((options) =>
{
	if (options.Name == "zhangsan")
	{
		options.Name = "zhangsan_a";
	}
});

效果如下:

第三段

if (_validations != null)
{
	var failures = new List<string>();
	foreach (var validate in _validations)
	{
		var result = validate.Validate(name, options);
		if (result.Failed)
		{
			failures.AddRange(result.Failures);
		}
	}
	if (failures.Count > 0)
	{
		throw new OptionsValidationException(name, typeof(TOptions), failures);
	}
}

这一段很显然就是来验证我们的配置是否符合规格。源码就不解释了,实践篇注重实践。

看下怎么用的吧。

services.AddOptions<SelfServiceOption>().Configure((options) =>
{
	if (options.Name == "zhangsan")
	{
		options.Name = "zhangsan_a";
	}
}).Validate(options =>
{
	return options.Name != "zhangsan_a";
});

然后就会报错。因为上面的逻辑是如果zhangsan,那么我把名字改成zhangsan_a,然后验证里面写名字不能等于zhangsan_a。

以上只是个人整理,如有错误,望请指点。

下一节,服务中的配置热更新。

posted @ 2021-06-06 19:20  敖毛毛  阅读(472)  评论(0编辑  收藏  举报