[ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]

Options[2]: 配置选项的正确使用方式[下篇] - 图1依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要。依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中。除了采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对象。

四、直接初始化Options对象

前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options模型。这个独立的Options模型本身并不依赖于配置系统,让配置系统来提供配置数据仅仅是通过Options模型的一个扩展点实现的。在很多情况下,可能并不需要将应用的配置选项定义在配置文件中,在应用启动时直接初始化可能是一种更方便快捷的方式。

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. var profile = new ServiceCollection()
  6. .AddOptions()
  7. .Configure<Profile>(it =>
  8. {
  9. it.Gender = Gender.Male;
  10. it.Age = 18;
  11. it.ContactInfo = new ContactInfo
  12. {
  13. PhoneNo = "123456789",
  14. EmailAddress = "foobar@outlook.com"
  15. };
  16. })
  17. .BuildServiceProvider()
  18. .GetRequiredService<IOptions<Profile>>()
  19. .Value;
  20.  
  21. Console.WriteLine($"Gender: {profile.Gender}");
  22. Console.WriteLine($"Age: {profile.Age}");
  23. Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
  24. Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}\n");
  25. }
  26. }

我们依然沿用前面演示的应用场景,现在摒弃配置文件,转而采用编程的方式直接对用户信息进行初始化,所以需要对程序做如上改写。在调用IServiceCollection接口的Configure<Profile>扩展方法时,不需要再指定一个IConfiguration对象,而是利用一个Action<Profile>类型的委托对作为参数的Profile对象进行初始化。程序运行后会在控制台上产生下图所示的输出结果。

7-1

具名Options同样可以采用类似的方式进行初始化。如果需要根据指定的名称对Options进行初始化,那么调用方法时就需要指定一个Action<TOptions,String>类型的委托对象,该委托对象的第二个参数表示Options的名称。在如下所示的代码片段中,我们通过类似的方式设置了两个用户(foo和bar)的信息,然后利用IOptionsSnapshot<TOptions>服务将它们分别提取出来。

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. var optionsAccessor = new ServiceCollection()
  6. .AddOptions()
  7. .Configure<Profile>("foo", it =>
  8. {
  9. it.Gender = Gender.Male;
  10. it.Age = 18;
  11. it.ContactInfo = new ContactInfo
  12. {
  13. PhoneNo = "123",
  14. EmailAddress = "foo@outlook.com"
  15. };
  16. })
  17. .Configure<Profile>("bar", it =>
  18. {
  19. it.Gender = Gender.Female;
  20. it.Age = 25;
  21. it.ContactInfo = new ContactInfo
  22. {
  23. PhoneNo = "456",
  24. EmailAddress = "bar@outlook.com"
  25. };
  26. })
  27. .BuildServiceProvider()
  28. .GetRequiredService<IOptionsSnapshot<Profile>>();
  29.  
  30. Print(optionsAccessor.Get("foo"));
  31. Print(optionsAccessor.Get("bar"));
  32.  
  33. static void Print(Profile profile)
  34. {
  35. Console.WriteLine($"Gender: {profile.Gender}");
  36. Console.WriteLine($"Age: {profile.Age}");
  37. Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
  38. Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}\n");
  39. };
  40. }
  41. }

该程序运行后会在控制台上产生下图所示的输出结果。在前面的演示中,我们利用依赖注入框架提供IOptions<TOptions>服务、IOptionsSnapshot<TOptions>服务和IOptionsMonitor<TOptions>服务,然后进一步利用它们来提供对应的Options对象。既然作为依赖注入容器的IServiceProvider对象能够提供这3个对象,我们就能够将它们注入消费Options对象的类型中。所谓的Options模式就是通过注入这3个服务来提供对应Options对象的编程模式。

7-2

五、根据依赖服务的Options设置

在很多情况下需要针对某个依赖的服务动态地初始化Options的设置,比较典型的就是根据当前的承载环境(开发、预发和产品)对Options做动态设置。《上篇》演示了一系列针对时间日期输出格式的配置,下面沿用这个场景演示如何根据当前的承载环境设置对应的Options。将DateTimeFormatOptions的定义进行简化,只保留如下所示的表示日期和时间格式的两个属性。

  1. public class DateTimeFormatOptions
  2. {
  3. public string DatePattern { get; set; }
  4. public string TimePattern { get; set; }
  5. public override string ToString() => $"Date: {DatePattern}; Time: {TimePattern}";
  6. }

如下所示的代码片段是整个演示实例的完整定义。我们利用第6章介绍的配置系统来设置当前的承载环境,具体采用的是基于命令行参数的配置源。.NET Core的承载系统通过IHostEnvironment接口表示承载环境,具体实现类型为HostingEnvironment。如下面的代码片段所示,我们利用获取的环境名称创建了一个HostingEnvironment对象,并针对IHostEnvironment接口采用Singleton生命周期做了相应的注册。

  1. class Program
  2. {
  3. public static void Main(string[] args)
  4. {
  5. var environment = new ConfigurationBuilder()
  6. .AddCommandLine(args)
  7. .Build() ["env"];
  8.  
  9. var services = new ServiceCollection();
  10. services
  11. .AddSingleton<IHostEnvironment>(new HostingEnvironment { EnvironmentName = environment })
  12. .AddOptions<DateTimeFormatOptions>().Configure<IHostEnvironment>( (options, env) => {
  13. if (env.IsDevelopment())
  14. {
  15. options.DatePattern = "dddd, MMMM d, yyyy";
  16. options.TimePattern = "M/d/yyyy";
  17. }
  18. else
  19. {
  20. options.DatePattern = "M/d/yyyy";
  21. options.TimePattern = "h:mm tt";
  22. }
  23. });
  24.  
  25. var options = services
  26. .BuildServiceProvider()
  27. .GetRequiredService<IOptions<DateTimeFormatOptions>>().Value;
  28. Console.WriteLine(options);
  29. }
  30. }

上面调用IServiceCollection接口的AddOptions<DateTimeFormatOptions>扩展方法完成了针对Options模型核心服务的注册和针对DateTimeFormatOptions的设置。该方法返回的是一个封装了IServiceCollection集合的OptionsBuilder<DateTimeFormatOptions>对象,可以调用其Configure<IHostEnvironment>方法利用提供的Action<DateTimeFormatOptions, IHostEnvironment>委托对象针对依赖的IHostEnvironment服务对DateTimeFormatOptions做相应的设置。具体来说,我们针对开发环境和非开发环境设置了不同的日期时间格式。如果采用命令行的方式启动这个应用程序,并利用命令行参数设置不同的环境名称,就可以在控制台上看到下图所示的针对DateTimeFormatOptions的不同设置。

7-5

六、验证Options的有效性

由于配置选项是整个应用的全局设置,为了尽可能避免错误的设置造成的影响,最好能够对内容进行有效性验证。接下来我们将上面的程序做了如下改动,从而演示如何对设置的日期和时间格式做最后的有效性验证。

  1. class Program
  2. {
  3. public static void Main(string[] args)
  4. {
  5. var config = new ConfigurationBuilder()
  6. .AddCommandLine(args)
  7. .Build();
  8. var datePattern = config["date"];
  9. var timePattern = config["time"];
  10.  
  11. var services = new ServiceCollection();
  12. services.AddOptions<DateTimeFormatOptions>()
  13. .Configure(options =>
  14. {
  15. options.DatePattern = datePattern;
  16. options.TimePattern = timePattern;
  17. })
  18. .Validate(options => Validate(options.DatePattern) && Validate(options.TimePattern),"Invalid Date or Time pattern.");
  19. try
  20. {
  21. var options = services
  22. .BuildServiceProvider()
  23. .GetRequiredService<IOptions<DateTimeFormatOptions>>().Value;
  24. Console.WriteLine(options);
  25. }
  26. catch (OptionsValidationException ex)
  27. {
  28. Console.WriteLine(ex.Message);
  29. }
  30.  
  31. static bool Validate(string format)
  32. {
  33. var time = new DateTime(1981, 8, 24,2,2,2);
  34. var formatted = time.ToString(format);
  35. return DateTimeOffset.TryParseExact(formatted, format, null, DateTimeStyles.None, out var value) && (value.Date == time.Date || value.TimeOfDay == time.TimeOfDay);
  36. }
  37. }
  38. }

上述演示实例借助配置系统以命令行的形式提供了日期和时间格式化字符串。在创建了OptionsBuilder<DateTimeFormatOptions>对象并对DateTimeFormatOptions做了相应设置之后,我们调用Validate<DateTimeFormatOptions>方法利用提供的Func<DateTimeFormatOptions,bool>委托对象对最终的设置进行验证。运行该程序并按照下图所示的方式指定不同的格式化字符串,系统会根据我们指定的规则来验证其有效性。

7-6

Options[2]: 配置选项的正确使用方式[下篇] - 图6

作者:蒋金楠微信公众账号:大内老A微博:www.weibo.com/artech如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号)。本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文:https://www.cnblogs.com/artech/p/inside-asp-net-core-06-02.html