跟我一起学.NetCore之Options实例演示及分析

前言

来啦!来啦!上一节一堆代码,是不是感觉甚是无味啊?没关系,这里结合上一节内容专注举例演示,绝不废话!走起~~~~~

正文

老规矩,一个WebApi项目走起,项目结构如下:

上一节中提到,Options是基于依赖注入的,所以我们需要将相关服务进行注册,如下:

注册完成之后就可以直接用啦,这里新建了一个ComputerController进行测试:

运行走起:

通常在使用的时候,相关服务的配置会放到配置文件中,比如数据库连接字符串等,所以在注册的时候将初始化的内容从配置中获取即可,如下:

跑起来~~~

通过以上的实例演示,可能会好奇为啥不直接用Configuration,我是这样理解的:通过Options的话,服务不限制使用初始化方式,根据需求选择,如果使用配置文件取值,服务也和配置没有直接关系,从而使得服务的使用没有过多限制;另外服务内部使用也比较便捷,就单纯操作Options对象;

哎呀,又来这一套,使用太简单了,来点真货呗,此时应该有小伙伴按捺不住了问:Options的相关服务是怎么注册的,初始化和上一节讲的初始化有啥关系? 来来来,不急,以下慢慢品~~~

那从哪开始呢? 

上一小节说的TOptions对象创建的三大步,创建内部直接New了(忘了的可以回顾一下上一节:跟我一起学.NetCore之选项(Options)核心类型简介),那这里就从注册初始化的地方下手:

Services.Configure方法中肯定有事,不然一句代码咋就让后面使用如此便捷呢,来,直接看源代码:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.


using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;


namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// Extension methods for adding configuration related options services to the DI container.
    /// </summary>
    public static class OptionsConfigurationServiceCollectionExtensions
    {
       // 挨着的这三个方法都是IServiceCollection 的扩展方法
        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
            => services.Configure<TOptions>(Options.Options.DefaultName, config);
        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
            => services.Configure<TOptions>(name, config, _ => { }); 
        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config, Action<BinderOptions> configureBinder)
            where TOptions : class
            => services.Configure<TOptions>(Options.Options.DefaultName, config, configureBinder);
        // 关键方法
        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));
            }
             // 注册Options相关服务,这是关键方法
            services.AddOptions();
            // IOptionsChangeTokenSource 注册,后续用于通知
            services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
            // 注册从配置系统中获取配置值的服务
            return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
        }
    }
}

上面代码中services.AddOptions()是关键方法,再翻代码瞅瞅:

 public static class OptionsServiceCollectionExtensions
 {
        // 恍然大悟吧,就是这个扩展方法,把服务都注册好了,所以使用的时候才直接注入即可
        public static IServiceCollection AddOptions(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }
            // 使用TryAdd的注入方式,为了对应服务类型只注册一次
            // 注册IOptions<>,实现类是OptionsManager<>
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
            // 注册IOptionsSnapshot<>,实现类是OptionsManager<>
            services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
            // 注册IOptionsMonitor<>,实现类是OptionsMonitor<>
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
            // 注册IOptionsFactory<> ,实现类是OptionsFactory<>
            services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
            // 注册IOptionsMonitorCache<>, 实现类是OptionsCache<>
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
            return services;
        }
        // 对应创建TOption 初始化的两小步,步骤1Configure
        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
            where TOptions : class
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }


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


            services.AddOptions();
            services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
            return services;
        }
        // 对应创建TOption 初始化的两小步,步骤2 PostConfigure
        public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
            where TOptions : class
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }


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


            services.AddOptions();
            services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions));
            return services;
        }
        ......省略了其他方法....
 }

看了以上代码,是不是豁然开朗了,Options的相关服务注册都是在AddOptions扩展方法中完成的,至于其中的Configure和PostConfigure实则对应着上一节的创建Options初始化的过程;以上注册的核心类型的生命周期需要了解一下,后面举例会关联到:

  • IOptions<>:Singleton - 单例模式

  • IOptionsSnapshot<>:Scoped - 作用域范围模式

  • IOptionsMonitor<>: Singleton - 单例模式

Configure是注册时进行初始化,而PostConfigure一般会用在读取到配置数据之后对数据进行加工,如下:

接下来说热更新,也就是配置改变,服务对应的Options能获取最新的值,代码稍微优化一下:

第一次访问,第二次访问前进行配置文件修改,然后在访问,运行结果如下:

可以看到,配置数据改变时,IOptionsSnapshot<>和IOptionsMonitor<>都能获取到最新的值,IOptions<>没有获取到,简单分析一下:

IOptons<>和IOptionsSnapshot<>其实内部是一样的,只是注入的生命周期不一样,前者是单例,后者是Scoped,这样就使得后者每次请求都是不同的IOptionsSnapshot对象,从而就能获取最新的值;而单例IOptions对象一致不变;那为什么单例的IOptionsMonitor类型能改变呢,那是因为IOptionsMonitor提供了监听改变的功能,上一节有简单说明;

如果在配置数据改变时,需要通知Option怎么办呢?通过IOptionsMonitor<TOptions>的OnChange监听改变,如下:

运行看结果,首先请求一次,然后修改配置文件,就能看到实时监控了:

最后来说说Options验证(也就是创建TOptions的第三步),主要是避免不合法的配置,导致程序在运行时业务逻辑出错才能发现错误,从而导致程序迭代周期频繁,用户体验差;验证有以下三种方式:

  • 直接注册验证函数

  • 实现IValidateOptions<TOptions>进行验证

  • 使用注解(Microsoft.Extensions.Options.DataAnnotations)

这里用三种方式分别依次验证Name,Cores,MemorySize 三个配置项,如下:

  • 直接注册验证函数

  • 实现IValidateOptions<TOptions>进行验证

  • 使用注解(Microsoft.Extensions.Options.DataAnnotations)


三种验证方式是相互独立的,可以单独使用,也可以组合使用;以上每一种方式对应图片内容;

运行访问不合法就报错,这样就避免配置不合法,导致业务逻辑不合法,如下

总结

Options(选项)的常规用法暂时就说这么多了,结合上一小节是不是感觉清晰多了~~~下一节说说日志(ILogger)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值