zoukankan      html  css  js  c++  java
  • 重新整理 .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。

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

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

  • 相关阅读:
    Your branch and 'origin/master' have diverged, and have # and # different commits each, respectively
    testng dataprovider 的几种用法以及Java中的二维数组
    python 类属性 实例属性 类方法 实例方法 静态方法(转载)
    Webdriver中PageFactory的正确用法
    Selenium webdriver在最开始打开的时候浏览器的地址栏会出现data的解决方法
    Selenium webdriver如何处理confirm对话框的问题
    SoapUI 引用第三方jar包和引用Groovy脚本
    git rebase -i 合并commit
    Git 撤销commit的注意事项
    单进程执行
  • 原文地址:https://www.cnblogs.com/aoximin/p/14852487.html
Copyright © 2011-2022 走看看