今天要讲的是OptionsModel解决方案,整个解决方案中也只有Microsoft.Framework.OptionsModel一个工程。按照表面文字OptionsModel应该翻译成选项模型,但是这个词没表现它实际的含义,我觉得称呼它为配置选项好些,不过为了原滋原味,我们还是用英文的:Configuration和OptionsModel表示它们。

什么是OptionsModel

在之前的配置文件一节([Asp.net 5] Configuration-新一代的配置文件)我们介绍过配置文件最后生成的是IConfiguration对象,但是IConfiguration可能包含很多信息混杂在一起。比如设置日志的等级Level、连接数据库的字符串connectionstring等。我们有时候不希望直接使用IConfiguration对象(比如对象中的配置项会变动),而是抽取一部分有用的信息以及一些其他值构成的具体的实体对象,那么这个从配置文件之后抽取的配置,就可以叫做OptionsModel。

简而言之:IConfiguration是配置文件的抽象;OptionsModel配置文件之后的配置,是直接对于系统的配置项。

OptionsModel的特点与实现

这类对象的特点很明显一般都是只包含一些属性,并无具体的内部逻辑;但是这种配置可能不是仅有一个:比如有数据库的connectionModel,而日志可能有LogOption。而且让这些OptionsModel实现统一的接口也不是现实的,也没有实际意义。那么如何实现Configuration到OptionsModel的转换呢?

答案很简单:Binder(神奇的Binder)。

        [Fact]
public void CanReadComplexProperties()
{
var dic = new Dictionary<string, string>
{
{"Integer", "-2"},
{"Boolean", "TRUe"},
{"Nested:Integer", ""}
};
var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
var config = builder.Build();
var options = ConfigurationBinder.Bind<ComplexOptions>(config);
Assert.True(options.Boolean);
Assert.Equal(-, options.Integer);
Assert.Equal(, options.Nested.Integer);
}

如何DI?

很多工程使用了DependencyInjection(依赖注入),而OptionsModel是比较基础的配置,几乎可以肯定内部用到OptionsModel的类会被注入,并且使用类型注册的方式注入。那么DependencyInjection内部肯定会递归到OptionsModel类,所以OptionsModel类也必须要进行注入,那么如何实现?

答曰:使用实例(Instance)直接注入到该类类型。[services.AddInstance(serviceType, type.Assembly.CreateInstance(type));]

如果我对于多个OptionsModel注册,但是我可以通过命名方式注入,还可以进行排序,那又该如何实现?

答曰:Microsoft.Framework.OptionsModel。

Microsoft.Framework.OptionsModel

类文件分类

在Microsoft.Framework.OptionsModel中,使用泛型的方式进行注入,之后以泛型的方式获取注入;但是注入的类和获取的泛型类确是不完全一致的。

  • 注入的类和接口:IConfigureOptions<in TOptions>、ConfigureOptions<TOptions>、ConfigureFromConfigurationOptions<TOptions>
  • 获取注入的类和接口:IOptions<out TOptions>、OptionsManager<TOptions>
  • 注册的扩展类: OptionsServiceCollectionExtensions
  • 其他:OptionsConstants

注入类和接口

IConfigureOptions<in TOptions>、ConfigureOptions<TOptions>、ConfigureFromConfigurationOptions<TOptions>这几个类和接口的关系为:

    public interface IConfigureOptions<in TOptions>
{
int Order { get; }
void Configure(TOptions options, string name = "");
}

IConfigureOptions

    public class ConfigureOptions<TOptions> : IConfigureOptions<TOptions>
{
public ConfigureOptions([NotNull]Action<TOptions> action)
{
Action = action;
} public Action<TOptions> Action { get; private set; } public string Name { get; set; } = "";
public virtual int Order { get; set; } = OptionsConstants.DefaultOrder; public virtual void Configure([NotNull]TOptions options, string name = "")
{
// Always invoke the action if no Name was specified, otherwise only if it was the requested name
if (string.IsNullOrEmpty(Name) || string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
{
Action.Invoke(options);
}
}
}

ConfigureOptions

    public class ConfigureFromConfigurationOptions<TOptions> : ConfigureOptions<TOptions>
{
public ConfigureFromConfigurationOptions([NotNull] IConfiguration config)
: base(options => ConfigurationBinder.Bind(options, config))
{
}
}

ConfigureFromConfigurationOptions

注入类和接口的注入

在OptionsServiceCollectionExtensions中提供了三种注册方式(实际是七个方法和重载):

  • 直接注册实现 IConfigureOptions<in TOptions>类型的数据。(假设实现类的类型为OptionsDev,则实际注册[services.AddTransient(IConfigureOptions<T>,OptionsDev)])
  • 提供Action<TOptions>进行注册。(创建实例instanceA=new ConfigureOptions<TOptions>(setupAction),之后注册[services.AddInstance(IConfigureOptions<T>,configureInstance)])
  • 提供IConfiguration进行注册。(创建实例instanceA=new ConfigureFromConfigurationOptions<TOptions>(setupAction),之后注册[services.AddInstance(IConfigureOptions<T>, configureInstance)])
    public static class OptionsServiceCollectionExtensions
{
public static IServiceCollection AddOptions([NotNull]this IServiceCollection services)
{
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
return services;
} private static bool IsAction(Type type)
{
return (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Action<>));
} private static IEnumerable<Type> FindIConfigureOptions(Type type)
{
var serviceTypes = type.GetTypeInfo().ImplementedInterfaces
.Where(t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == typeof(IConfigureOptions<>));
if (!serviceTypes.Any())
{
string error = "TODO: No IConfigureOptions<> found.";
if (IsAction(type))
{
error += " did you mean Configure(Action<T>)";
}
throw new InvalidOperationException(error);
}
return serviceTypes;
} public static IServiceCollection ConfigureOptions([NotNull]this IServiceCollection services, Type configureType)
{
var serviceTypes = FindIConfigureOptions(configureType);
foreach (var serviceType in serviceTypes)
{
services.AddTransient(serviceType, configureType);
}
return services;
} public static IServiceCollection ConfigureOptions<TSetup>([NotNull]this IServiceCollection services)
{
return services.ConfigureOptions(typeof(TSetup));
} public static IServiceCollection ConfigureOptions([NotNull]this IServiceCollection services, [NotNull]object configureInstance)
{
var serviceTypes = FindIConfigureOptions(configureInstance.GetType());
foreach (var serviceType in serviceTypes)
{
services.AddInstance(serviceType, configureInstance);
}
return services;
} public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
[NotNull] Action<TOptions> setupAction,
string optionsName)
{
return services.Configure(setupAction, OptionsConstants.DefaultOrder, optionsName);
} public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
[NotNull] Action<TOptions> setupAction,
int order = OptionsConstants.DefaultOrder,
string optionsName = "")
{
services.ConfigureOptions(new ConfigureOptions<TOptions>(setupAction)
{
Name = optionsName,
Order = order
});
return services;
} public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
[NotNull] IConfiguration config, string optionsName)
{
return services.Configure<TOptions>(config, OptionsConstants.ConfigurationOrder, optionsName);
} public static IServiceCollection Configure<TOptions>([NotNull]this IServiceCollection services,
[NotNull] IConfiguration config,
int order = OptionsConstants.ConfigurationOrder,
string optionsName = "")
{
services.ConfigureOptions(new ConfigureFromConfigurationOptions<TOptions>(config)
{
Name = optionsName,
Order = order
});
return services;
}
}

OptionsServiceCollectionExtensions

我们可以看到OptionsServiceCollectionExtensions内部使用的是泛型注册,不同的方法只是注册的方式不一样,最终结果注册的都是IConfigureOptions<T>对象。

获取注入的类和接口
OptionsServiceCollectionExtensions中获取方式的注册:

  • services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));

我们获取的时候,IOptions<T>,那么获取的一定是OptionsManager<T>对象,那么我们看下相关的实现代码:

    public interface IOptions<out TOptions> where TOptions : class,new()
{
TOptions Options { get; }
TOptions GetNamedOptions(string name);
}

IOptions

    public class OptionsManager<TOptions> : IOptions<TOptions> where TOptions : class,new()
{
private object _mapLock = new object();
private Dictionary<string, TOptions> _namedOptions = new Dictionary<string, TOptions>(StringComparer.OrdinalIgnoreCase);
private IEnumerable<IConfigureOptions<TOptions>> _setups; public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups)
{
_setups = setups;
} public virtual TOptions GetNamedOptions([NotNull] string name)
{
if (!_namedOptions.ContainsKey(name))
{
lock (_mapLock)
{
if (!_namedOptions.ContainsKey(name))
{
_namedOptions[name] = Configure(name);
}
}
}
return _namedOptions[name];
} public virtual TOptions Configure(string optionsName = "")
{
return _setups == null
? new TOptions()
: _setups.OrderBy(setup => setup.Order)
.Aggregate(new TOptions(),
(options, setup) =>
{
setup.Configure(options, optionsName);
return options;
});
} public virtual TOptions Options
{
get
{
return GetNamedOptions("");
}
}
}

OptionsManager

由于OptionsManager是使用类类型进行注入的,所以一定会调用OptionsManager类的构造函数,但是构造函数中包含参数(类型为IEnumerable<IConfigureOptions<T>>),那么系统就会递归获取其参数的注入方式。那么我们就会获取到所有IConfigureOptions<T>的注入项。之后我们就可以根据我们获取到的IConfigureOptions<T>类型,进行相关的排序,获取实例。

获取IEnumerable<T>的注入方式,请详细见( [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)

OptionsManager详解

对于OptionsManager类中_setups是已经获取到的IConfigureOptions<T>集合。那么我们获取Options/GetNamedOptions([NotNull] string name)时候,实际上内部在第一次会执行Configure方法

        public virtual TOptions Configure(string optionsName = "")
{
return _setups == null
? new TOptions()
: _setups.OrderBy(setup => setup.Order)
.Aggregate(new TOptions(),
(options, setup) =>
{
setup.Configure(options, optionsName);
return options;
});
}

该方法首先判断setups是否有值。如果没值创建新的;如果有值,则排序后,进行函数聚合。

 Aggregate聚合函数:该函数包含俩个参数初始值T A,返回为T类型的Func(T,item,T)。系统会便利内部的所有item,之后使用初始值A,以及当前item进行函数计算,之后返回A‘,然后将A’赋值A,进行下一次循环,最后得到的A进行返回。

测试用例

    public class FakeOptions
{
public FakeOptions()
{
Message = "";
} public string Message { get; set; }
}
public class FakeOptionsSetupA : ConfigureOptions<FakeOptions>
{
public FakeOptionsSetupA() : base(o => o.Message += "A")
{
Order = -;
}
} public class FakeOptionsSetupB : ConfigureOptions<FakeOptions>
{
public FakeOptionsSetupB() : base(o => o.Message += "B")
{
Order = ;
}
} public class FakeOptionsSetupC : ConfigureOptions<FakeOptions>
{
public FakeOptionsSetupC() : base(o => o.Message += "C")
{
Order = ;
}
}

测试基础类

        public void SetupCallsSortedInOrder()
{
var services = new ServiceCollection().AddOptions();
var dic = new Dictionary<string, string>
{
{"Message", "!"},
};
var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
var config = builder.Build();
services.Configure<FakeOptions>(o => o.Message += "Igetstomped", -);
services.Configure<FakeOptions>(config);
services.Configure<FakeOptions>(o => o.Message += "a", -);
services.ConfigureOptions<FakeOptionsSetupC>();
services.ConfigureOptions(new FakeOptionsSetupB());
services.ConfigureOptions(typeof(FakeOptionsSetupA));
services.Configure<FakeOptions>(o => o.Message += "z", ); var service = services.BuildServiceProvider().GetService<IOptions<FakeOptions>>();
Assert.NotNull(service);
var options = service.Options;
Assert.NotNull(options);
Assert.Equal("!aABCz", options.Message);
}
public void NamedSetupDoNotCollideWithEachOther()
{
var services = new ServiceCollection().AddOptions();
var dic = new Dictionary<string, string>
{
{"Message", "!"},
};
var builder = new ConfigurationBuilder(new MemoryConfigurationSource(dic));
var config = builder.Build(); services.ConfigureOptions(new FakeOptionsSetupB { Name = "" });
services.Configure<FakeOptions>(o => o.Message += "Z", , ""); services.ConfigureOptions(new FakeOptionsSetupB { Name = "" });
services.Configure<FakeOptions>(config, "");
services.Configure<FakeOptions>(o => o.Message += "z", , ""); var service = services.BuildServiceProvider().GetService<IOptions<FakeOptions>>();
Assert.NotNull(service);
var options = service.Options;
Assert.NotNull(options);
Assert.Equal("", options.Message); var options2 = service.GetNamedOptions("");
Assert.NotNull(options2);
Assert.Equal("BZ", options2.Message); var options3 = service.GetNamedOptions("");
Assert.NotNull(options3);
Assert.Equal("!Bz", options3.Message); }

[Asp.net 5] Options-配置文件之后的配置的更多相关文章

  1. asp.net core 将配置文件配置迁移到数据库(一)

    asp.net core 将配置文件配置迁移到数据库(一) Intro asp.net core 配置默认是项目根目录下的 appsettings.json 文件,还有环境变量以及 command l ...

  2. App.config和Web.config配置文件的自定义配置节点

    前言 昨天修改代码发现了一个问题,由于自己要在WCF服务接口中添加了一个方法,那么在相应调用的地方进行更新服务就可以了,不料意外发生了,竟然无法更新.左查右查终于发现了问题.App.config配置文 ...

  3. Asp.NetCore源码学习[1-2]:配置[Option]

    Asp.NetCore源码学习[1-2]:配置[Option] 在上一篇文章中,我们知道了可以通过IConfiguration访问到注入的ConfigurationRoot,但是这样只能通过索引器IC ...

  4. ASP.NET Core 在 JSON 文件中配置依赖注入

    前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...

  5. “Options模式”下的配置是如何绑定为Options对象

    “Options模式”下的配置是如何绑定为Options对象 配置的原子结构就是单纯的键值对,并且键和值都是字符串,但是在真正的项目开发中我们一般不会单纯地以键值对的形式来使用配置.值得推荐的做法就是 ...

  6. asp.net 网站在Apache下的配置,就这么简单

    asp.net 网站在Apache下的配置,就这么简单 # # Virtual Hosts # # If you want to maintain multiple domains/hostnames ...

  7. Log4Net 在ASP.NET WebForm 和 MVC的全局配置

    使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能改变 ...

  8. ASP.NET中的配置文件

    ASP.NET中的配置文件 原创 2014年10月13日 08:15:27 1199   在机房收费系统的时候曾经应用过配置文件,当时也就那么一用对配置文件了解的不是很透彻,下面就来总结一下有关配置文 ...

  9. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  10. asp.net 站点在Apache下的配置,就这么简单

    asp.net 站点在Apache下的配置,就这么简单 # # Virtual Hosts # # If you want to maintain multiple domains/hostnames ...

随机推荐

  1. 用jdbc访问二进制类型的数据

    package it.cast.jdbc; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; impor ...

  2. 安卓刷机--fastboot线刷

    首先需要下载fastboot.exe,copy到system32文件夹下. 对于安卓系统的智能手机,同时按住开机键和音量减键,或手机连上电脑,输入adb reboot bootloader进入fast ...

  3. SQL Server差异备份的备份/还原原理

    SQL Server差异备份的备份/还原原理 记住一点:差异备份是基于最后一次完整备份的差异,而不是基于最后一次差异的差异   备份过程: 1-完整备份之后有无对数据库做过修改,如果有,记录数据库的最 ...

  4. 编写具有单一职责(SRP)的类

    这两周我需要对一个历史遗留的功能做一些扩展,正如很多人不愿意碰这些历史遗留的代码一样,我的内心也同样对这样的任务充满反抗.这些代码中充斥着各种null判断(你写的return null正确吗?),不规 ...

  5. Hadoop学习笔记—4.初识MapReduce

    一.神马是高大上的MapReduce MapReduce是Google的一项重要技术,它首先是一个编程模型,用以进行大数据量的计算.对于大数据量的计算,通常采用的处理手法就是并行计算.但对许多开发者来 ...

  6. 日志系统实战(三)-分布式跟踪的Net实现

    介绍 在大型系统开发调试中,跨系统之间联调开始变得不好使了.莫名其妙一个错误爆出来了,日志虽然有记录,但到底是哪里出问题了呢? 是Ios端参数传的不对?还是A系统或B系统提供的接口导致?相信有不少人遇 ...

  7. 微软CMS项目 Orchard 所用到的开源项目

    研发了Orchard一年左右了,时常遇到瓶颈,总觉得力不从心,其实并不是基础不够,关键还是概念性的东西太多,一会儿这个概念名词,一会那个,关于Orchard的技术文档也的确很少,每次看起来总是焦头烂额 ...

  8. gulp使用小结(一)

    这篇文章不会介绍 gulp 的起源.发展:不会去一个个讲解 gulp API:也不想出现大段大段的 gulpfile.js 代码:更木有帮你分析 gulp 实现原理,只有一些我自己对 gulp 的使用 ...

  9. ASP.NET MVC 5 - 将数据从控制器传递给视图

    在我们讨论数据库和数据模型之前,让我们先讨论一下如何将数据从控制器传递给视图.控制器类将响应请求来的URL.控制器类是给您写代码来处理传入请求的地方,并从数据库中检索数据,并最终决定什么类型的返回结果 ...

  10. 无法启用插件,因为它引起了一个致命错误(fatal error)。

    关于wordpress不能启用某插件引发的错误,php 中 出错,Cannot redeclare wpb_getImageBySize().这个问题也是在我wordpress版本从v4.1生成v4. ...