【5min+】更好的选项实践。.Net Core中的IOptions

简介:

【5min+】更好的选项实践。.Net Core中的IOptions
系列介绍
【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。

通过本篇文章您将Get:

不在AspNet Core的Startup.cs中完成mvc的选项配置(比如在其它地方为MVC添加过滤器等操作)
了解Options的使用
了解IOptions、IOptionsMonitor、IOptionsSnapshot的区别
时长为五分钟以内,建议先投币再上车观看😜

正文
.NET Core为咱们提供的默认依赖注入方式[Microsoft.Extensions.DependencyInjection]相对来说功能已经很完善了,虽然有一些功能没有实现(比如在使用factory进行注册时无法获取type等),但并不影响我们令接口与实现进行分离。

某些情况下,您会发现,当我们的业务类被添加到依赖注入容器中时,该类构造函数中所依赖的其它类都得一同添加到容器(虽然有某些奇技淫巧可以规避,但是构造函数注入依旧是规范的手段)。可是,我的一些依赖类为选型类型怎么办呢?比如下面的代码:

复制代码
public class MyBusinessClass
{

public MyBusinessClass(SomeOptions options)
{
    if (options.ShouldOpenTCP)
        //do something.....

    if (options.ShouldLogIndo)
        // do something
}

}
SomeOptions是一个典型的选项项类型,我们通过它公开的一些属性来对项目进行配置。而当MyBusinessClass被注入到容器的时候,意味着SomeOptions也需要被注入。

对于这种选项类型,微软给出了专门的处理手段:Microsoft.Extensions.Options包。我们只需要使用该包为IServiceCollection提供的扩展方法AddOptions()就可以完成注入选项:

复制代码
services.AddOptions();

public class MyBusinessClass
{

public MyBusinessClass(IOptions<SomeOptions> options)
{
    SomeOptions value = options.Value;
}

}
看起来这和上面的代码好像区别也不是很大吧。都是把SomeOptions添加到容器中,那么第二种方法和第一种方法比起来有什么优点呢?微软专门推出该方式难道只是为了“年底冲业绩”?

非也非也?第二种方式其实用了更好的解耦思想来设计。假如咱们的SomeOptions需要在其它模块中修改怎么办? 如果用第一种直接注入到容易的方案的话,这就十分的困难。而使用AddOptions的方式您就可以轻而易举。

Microsoft.Extensions.Options提供了IConfigureOptions和IPostConfigureOptions这两种类似于生命周期钩子的接口,让您能够在读取选项的时候,进行某些操作。

在AspNetCore中试一试
在AspnetCore中就有一个很明显的选项:MvcOptions,该选项提供了咱们配置MVC项目的各种各样的参数。

复制代码
//Startup.cs

public void ConfigureServices(IServiceCollection services)
{

services.AddControllers(options =>
{
    options.Filters.Add(new MyFileter());
});

}
上面代码是我们在Startup.cs中配置MvcOptions最最常见的步骤,这里我用添加一个全局过滤器来举例。

如果我不想在Startup.cs中添加这句代码怎么办呢? 比如我写了一个第三方的库,库中包含了N个过滤器,我肯定没有办法要求用户在使用该库的时候将这N个过滤器一个一个的添加到options中。(这里只是假设,虽然可以使用特性的方式来完成同样的过滤器功能)

这个时候就可以拿出我们上面讲的一大杀器:IConfigureOptions.

复制代码
internal class MvcOptionsConfigure : IConfigureOptions
{

public void Configure(MvcOptions options)
{
    options.Filters.Add(new MyFileter());
}

}

services.AddSingleton, MvcOptionsConfigure>();
这样就完成了关注点的分离,我们不需要一直死守着Startup.cs文件不放,也不需要让用户手动去配置。只要我们知道IServiceCollection就可以往里面添加我们自己的业务点。当然,Microsoft.Extensions.Options包还提供了另外的方式让您可以完成IConfigureOptions的同样操作,不过这些操作都是像语法糖一样,实质上是相同的:

复制代码
//和上面同样的功能
services.Configure(Options =>
{

 options.Filters.Add(new MyFileter());

});
IOptions、IOptionsMonitor和IOptionsSnapshot
在上面其实我们已经见过了IOptions的尊容,我们可以通过注入IOptions来获取MyOptions实例。

但是!但是!但是!!!! IOptions还有两个兄弟IOptionsMonitor和IOptionsSnapshot。光名字上长的就很像了,它们都还有类似于“Value”的属性来获取选项实例。

妈呀,那么它们到底有什么不同呢?什么时候该用老大,什么使用该用老二呢? 接下来,年度最佳找不同大戏即将开始………………

先来看看IOptions和IOptionsSnapshot吧,看看它们的接口定义:

复制代码
///
/// Used to access the value of for the lifetime of a request.
///
/// Options type.
public interface IOptionsSnapshot : IOptions where TOptions : class, new()
{

/// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given name.
/// </summary>
TOptions Get(string name);

}
我天,居然IOptionsSnapshot还继承了IOptions,而且只是多了一个Get方法,那么是否这两个类其实很相似呢?我们直接来看看源码:

复制代码
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
纳尼?这都还不是相似不相似的问题,这TM不是同一个实现吗?只是接口类型不同而已,实现都是OptionsManager<>。 那为啥要搞两个不同的接口。

等等(手动播放名侦探bgm),这俩生命周期咋不一样? 一个是Singleton一个是Scoped。而再来看IOptionsSnapshot的说明:“Used to access the value of TOptions for the lifetime of a request.”(用于在请求的生存期内访问选项的值)。

原来如此,这样看来就很清晰了。它俩的区别其实就是依赖注入的生命周期不同而已,为单例的IOptions意味着,只要您注入之后以后获取的都是同一个实例,而IOptionsSnapshot呢,作为Scoped级别,每再一个新的Scoped中获取它一次,它就会请求一个新的实例。

所以来举个例子,在AspNet Core中咱们某个选项的值是根据一个文件的某个值来的。刚开始文本的值是“A”,咱们在运行AspNet Core之后我们获取IOptions和IOptionsSnapshot,此时得到的MyOptions的该属性的值都是"A"。但是假如我们更改了文本的值,改为“B”。如果在发起一个http请求去获取MyOptions的结果,此时IOptions依旧是“A”,而IOptionsSnapshot则更改为了B。

原因很简单,因为IOptions是单例的,所以从程序一开始加载过一次之后,以后访问它都是这个结果,而IOptionsSnapshot是Scoped级别的,所以每一个新的Scoped时都会又去访问文本文件获取值,而一次Http请求就会开启一次新的Scoped,所以此时结果就成为“B”。这个时候我们大概就能读懂上面IOptionsSnapshot<>接口的解释了:“用于在请求的生存期内访问选项的值”。

三兄弟一下就干掉了俩,接下来看看最后一个好兄弟(毒瘤):IOptionsMonitor。还是直接看它的源代码呢:

复制代码
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
纳尼?单例? 那是不是意味着它也一样,一旦启动了之后还是保持原有的结果呢?先不急,看看它的接口定义再说:

复制代码
///
/// Used for notifications when instances change.
///
/// The options type.
public interface IOptionsMonitor
{

/// <summary>
/// Returns the current <typeparamref name="TOptions"/> instance with the <see cref="Options.DefaultName"/>.
/// </summary>
TOptions CurrentValue { get; }

/// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given name.
/// </summary>
TOptions Get(string name);

/// <summary>
/// Registers a listener to be called whenever a named <typeparamref name="TOptions"/> changes.
/// </summary>
/// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param>
/// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns>
IDisposable OnChange(Action<TOptions, string> listener);

}
可以看出它自己是一个单独的接口,并不像其它俩兄弟是继承关系。而且该接口居然有一个OnChange签名?而且该方法需要一个Action的参数。

握草(继续手动播放名侦探bgm),如果您有幸看过我的上一篇文章:《【5min+】 一个令牌走天下!.Net Core中的ChangeToken》,那么您可能一下就知道它扮演了什么样的角色。(5min+系列居然是连续的.... 😭)

再看看该接口的说明:"Used for notifications when TOptions instances change."(用于在选项实例更改时进行通知)。果然和我们猜的一模一样,那么它的实现类里面一定有咱们上一篇文章中提到的:ChangeToken和IChangeToken等东西。

来吧,扒开它的具体实现,验证咱们的猜想:

复制代码
public OptionsMonitor(IOptionsFactory factory, IEnumerable> sources, IOptionsMonitorCache cache)
{

_factory = factory;
_sources = sources;
_cache = cache;

foreach (var source in _sources)
{
    var registration = ChangeToken.OnChange(
            () => source.GetChangeToken(),
            (name) => InvokeChanged(name),
            source.Name);

    _registrations.Add(registration);
}

}
意料之中,也就是说IOptionsMonitor<>的注入级别虽然是单例,但是因为它具有IChangeToken的实现,所以它能够在选项源改变的时候,“立马对选项做出对应的改变”。而改变依赖于IOptionsChangeTokenSource这个令牌源,目前.net core对很多常用工具都实现了该令牌源,比如Logger,Configuration等。所以当我们某个选项依赖于IConfiguration(appsetting.json)的某一项时,当修改appsetting.json文件,该选项的值就能够立马得到更改。

所以来回过头来看这三兄弟。它们的区别其实在于变更的时效性:

类型 说明 时效性
IOptions 一旦程序启动,该选项的值就无法更改 无时效性可言
IOptionsSnapshot 当开启一个新Scoped时,就会重新计算选项的值 相对比较低,依赖于合适开启一个新的Scoped
IOptionsMonitor 依赖于IChangeToken,只要令牌源变更则立刻做出反应 高
假如把IOptionsMonitor添加到上面IOptions和IOptionsSnapshot的文件变更案例,如果在一次HTTP请求中,文件变更了两次,那么IOptionsSnapshot不会在第二次更改中同步更改,而IOptionsMonitor则可以。

那么什么时候来使用什么样的接口呢?相信这个时候,您的心里比我还要清楚。当您的选项只是负责一次性处理的时候,应用启动了就不需要更改,那么考虑使用IOptions,如果是对数据源的变更要求很严格,比如开启了一个“BackgroundJob”在后台运行,该job需要一个选项类型,而该类型依赖于配置文件,需要对配置文件更改时即刻做出改变,那么请考虑使用IOptionsMonitor。

最后回过头来看微软官方文档上关于“Options”的两个点(ISP和关注点分离),您应该一下就能理解。

原文地址https://www.cnblogs.com/uoyo/p/12583149.html

相关文章
|
22天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
39 5
|
3月前
|
存储 开发框架 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`,优化了内存使用和序列化速度。
100 0
|
2月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
46 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
30天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
27 3
|
7天前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
25 0
|
4月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
4月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
117 3
|
3月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3月前
|
开发框架 缓存 算法
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
|
3月前
|
Cloud Native API C#
.NET云原生应用实践(一):从搭建项目框架结构开始
.NET云原生应用实践(一):从搭建项目框架结构开始