ASP.NET Core : 二十三. 深入聊一聊配置的内部处理机制(三)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 上一章介绍了配置的多种数据源被注册、加载和获取的过程,本节看一下这个过程系统是如何实现的。

二、数据源的加载

从图18‑5可知,所有类型数据源最终创建的XXXConfigurationProvider都继承自ConfigurationProvider,所以它们都有一个Load方法和一个IDictionary<string, string> 类型的Data 属性,它们是整个配置系统的重要核心。Load方法用于数据源的数据的读取与处理,而Data用于保存最终结果。通过逐一调用Provider的Load方法完成了整个配置系统的数据加载。


以JsonConfigurationProvider为例,它继承自FileConfigurationProvider,所以先看一下FileConfigurationProvider的代码:

public abstract class FileConfigurationProvider : ConfigurationProvider
{
//省略部分代码
    private void Load(bool reload)
    {
        var file = Source.FileProvider?.GetFileInfo(Source.Path);
        if (file == null || !file.Exists)
        {
        //省略部分代码
        }
        else
        {
            if (reload)
            {
                Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            }
            using (var stream = file.CreateReadStream())
            {
                try
                {
                    Load(stream);
                }
                catch (Exception e)
                {
//省略部分代码
                }
            }
        }
        OnReload();
    }
    public override void Load()
    {
        Load(reload: false);
}
    public abstract void Load(Stream stream);
} 

本段代码的主要功能就是读取文件生成stream,然后调用Load(stream)方法解析文件内容。从图18‑5可知,JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider都是继承自FileConfigurationProvider,而对应JSON、INI、XML三种数据源来说,只是文件内容的格式不同,所以将通用的读取文件内容的功能交给了FileConfigurationProvider来完成,而这三个子类的ConfigurationProvider只需要将FileConfigurationProvider读取到的文件内容的解析即可。所以这个参数为stream 的Load方法写在JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider这样的子类中,用于专门处理自身对应的格式的文件。


JsonConfigurationProvider代码如下:

public class JsonConfigurationProvider : FileConfigurationProvider
{
    public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
    public override void Load(Stream stream)
    {
        try
        {
            Data = JsonConfigurationFileParser.Parse(stream);
        }
        catch (JsonReaderException e)
        {
            string errorLine = string.Empty;
            if (stream.CanSeek)
            {
                stream.Seek(0, SeekOrigin.Begin);
                IEnumerable<string> fileContent;
                using (var streamReader = new StreamReader(stream))
                {
                    fileContent = ReadLines(streamReader);
                    errorLine = RetrieveErrorContext(e, fileContent);
                }
            }
            throw new FormatException(Resources.FormatError_JSONParseError(e.LineNumber, errorLine), e);
        }
    }
   //省略部分代码
}

JsonConfigurationProvider中关于JSON文件的解析由JsonConfigurationFileParser.Parse(stream)完成的。最终的解析结果被赋值给了父类ConfigurationProvider的名为Data的属性中。


所以最终每个数据源的内容都分别被解析成了IDictionary<string, string>集合,这个集合作为对应的ConfigurationProvider的一个属性。而众多ConfigurationProvider组成的集合又作为ConfigurationRoot的属性。最终它们的关系图如下图3:


13.png

13.png

图3

到此,配置的加载与数据的转换工作完成。下图4展示了这个过程。

14.png

图4


三、配置的读取


第一节的例子中,通过_configuration["Theme:Color"]的方式获取到了对应的配置值,这是如何实现的呢?现在我们已经了解了数据源的加载过程,而这个_configuration就是数据源被加载后的最终产出物,即ConfigurationRoot,见图18‑7。它的代码如下:

public class ConfigurationRoot : IConfigurationRoot
{
    private IList<IConfigurationProvider> _providers;
    private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
    //省略了上文已讲过的构造方法
    public IEnumerable<IConfigurationProvider> Providers => _providers;
    public string this[string key]
    {
        get
        {
            foreach (var provider in _providers.Reverse())
            {
                string value;
                if (provider.TryGet(key, out value))
                {
                    return value;
                }
            }
            return null;
        }
        set
        {
            if (!_providers.Any())
            {
                throw new InvalidOperationException(Resources.Error_NoSources);
            }
            foreach (var provider in _providers)
            {
                provider.Set(key, value);
            }
        }
    }
    public IEnumerable<IConfigurationSection> GetChildren() => GetChildrenImplementation(null);
    internal IEnumerable<IConfigurationSection> GetChildrenImplementation(string path)
    {
        return _providers
            .Aggregate(Enumerable.Empty<string>(),
                (seed, source) => source.GetChildKeys(seed, path))
            .Distinct()
            .Select(key => GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
    }
    public IChangeToken GetReloadToken() => _changeToken;
    public IConfigurationSection GetSection(string key) 
        => new ConfigurationSection(this, key);
    public void Reload()
    {
        foreach (var provider in _providers)
        {
            provider.Load();
        }
        RaiseChanged();
    }
    private void RaiseChanged()
    {
        var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }
}

对应_configuration["Theme:Color"]的读取方式的是索引器“string this[string key]”,通过查看其get方法可知,它是通过倒序遍历所有ConfigurationProvider,在ConfigurationProvider的Data中尝试查找是否存在Key为"Theme:Color"的值。这也说明了第一节的例子中,在Theme.json中设置了Theme对象的值后,原本在appsettings.json设置的Theme的值被覆盖的原因。从图18‑6中可以看到,该值其实也是被读取并加载的,只是由于ConfigurationRoot的“倒序”遍历ConfigurationProvider的方式导致后注册的Theme.json中的Theme值先被查找到了。同时验证了所有配置值均认为是string类型的约定。


ConfigurationRoot还有一个GetSection方法,会返回一个IConfigurationSection对象,对应的是ConfigurationSection类。它的代码如下:

public class ConfigurationSection : IConfigurationSection
    {
        private readonly ConfigurationRoot _root;
        private readonly string _path;
        private string _key;
        public ConfigurationSection(ConfigurationRoot root, string path)
        {
            if (root == null)
            {
                throw new ArgumentNullException(nameof(root));
            }
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }
            _root = root;
            _path = path;
        }
        public string Path => _path;
        public string Key
        {
            get
            {
                if (_key == null)
                {
                    // Key is calculated lazily as last portion of Path
                    _key = ConfigurationPath.GetSectionKey(_path);
                }
                return _key;
            }
        }
        public string Value
        {
            get
            {
                return _root[Path];
            }
            set
            {
                _root[Path] = value;
            }
        }
        public string this[string key]
        {
            get
            {
                return _root[ConfigurationPath.Combine(Path, key)];
            }
            set
            {
                _root[ConfigurationPath.Combine(Path, key)] = value;
            }
        }
        public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));
        public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);
        public IChangeToken GetReloadToken() => _root.GetReloadToken();
}

它的代码很简单,可以说没有什么实质的代码,它只是保存了当前路径和对ConfigurationRoot的引用。它的方法大多是通过调用ConfigurationRoot的对应方法完成的,通过它自身的路径计算在ConfigurationRoot中对应的Key,从而获取对应的值。而ConfigurationRoot对配置值的读取功能以及数据源的重新加载功能(Reload方法)也是通过ConfigurationProvider实现的,实际数据也是保存在ConfigurationProvider的Data值中。所以ConfigurationRoot和ConfigurationSection就像一个外壳,自身并不负责数据源的加载(或重载)与存储,只负责构建了一个配置值的读取功能。


目录
相关文章
|
2月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
2月前
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
30 1
|
3月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
43 7
|
3月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
64 0
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
49 0
|
4月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?
|
4月前
|
开发框架 .NET
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
129 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
195 0