- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
如何在静态方法中访问DI容器长期以来一直都是一个令人苦恼的问题,特别是对于热爱编写扩展方法的朋友。之所以会为这个问题苦恼,是因为一个特殊的服务生存期——范围内(Scoped),所谓的Scoped就是范围内单例,最常见的WebAPI/MVC中一个请求对应一个范围,所有注册为Scoped的对象在同一个请求中是单例的。如果仅仅用一个静态字段存储应用启动时创建出的IServiceProvider对象,那么在一个请求中通过该字段是无法正确获取当前请求中创建的Scoped对象的.
在早些时候有针对肉夹馍(Rougamo)访问DI容器发布了一些列NuGet,由于肉夹馍不仅能应用到实例方法上还能够应用到静态方法上,所以肉夹馍访问DI容器的根本问题就是如何在静态方法中访问DI容器。考虑到静态方法访问DI容器是一个常见的公共问题,所以现在将核心逻辑抽离成一系列单独的NuGet包,方便不使用肉夹馍的朋友使用.
启动项目引用DependencyInjection.StaticAccessor.Hosting 。
dotnet add package DependencyInjection.StaticAccessor.Hosting 。
非启动项目引用DependencyInjection.StaticAccessor 。
dotnet add package DependencyInjection.StaticAccessor 。
// 1. 初始化。这里用通用主机进行演示,其他类型项目后面将分别举例
var builder = Host.CreateDefaultBuilder();
builder.UsePinnedScopeServiceProvider(); // 仅此一步完成初始化
var host = builder.Build();
host.Run();
// 2. 在任何地方获取
class Test
{
public static void M()
{
var yourService = PinnedScope.ScopedServices.GetService<IYourService>();
}
}
如上示例,通过静态属性PinnedScope.ScopedServices即可获取当前Scope的IServiceProvider对象,如果当前不在任何一个Scope中时,该属性返回根IServiceProvider.
由于DependencyInjection.StaticAccessor的实现包含了通过反射访问微软官方包非public成员,官方的内部实现随着版本的迭代也在不断地变化,所以针对官方包不同版本发布了对应的版本。DependencyInjection.StaticAccessor的所有NuGet包都采用语义版本号格式(SemVer),其中主版本号与Microsoft.Extensions.*相同,次版本号为功能发布版本号,修订号为BUG修复及微小改动版本号。请各位在安装NuGet包时选择与自己引用的Microsoft.Extensions.*主版本号相同的最新版本.
另外需要说明的是,由于我本地创建blazor项目时只能选择.NET8.0,所以blazor相关包仅提供了8.0版本,如果确实有低版本的需求,可以到github中提交issue.
启动项目引用DependencyInjection.StaticAccessor.Hosting 。
dotnet add package DependencyInjection.StaticAccessor.Hosting 。
非启动项目引用DependencyInjection.StaticAccessor 。
dotnet add package DependencyInjection.StaticAccessor 。
var builder = WebApplication.CreateBuilder();
builder.Host.UsePinnedScopeServiceProvider(); // 唯一初始化步骤
var app = builder.Build();
app.Run();
Blazor的DI Scope是一个特殊的存在,在WebAssembly模式下Scoped等同于单例;而在Server模式下,Scoped对应一个SignalR连接。针对Blazor的这种特殊的Scope场景,除了初始化操作,还需要一些额外操作.
我们知道,Blazor项目在创建时可以选择交互渲染模式,除了Server模式外,其他的模式都会创建两个项目,多出来的这个项目的名称以.Client结尾。这里我称.Client项目为Client端项目,另一个项目为Server端项目(Server模式下唯一的那个项目也称为Server端项目).
安装NuGet 。
启动项目引用DependencyInjection.StaticAccessor.Blazor 。
dotnet add package DependencyInjection.StaticAccessor.Blazor 。
非启动项目引用DependencyInjection.StaticAccessor 。
dotnet add package DependencyInjection.StaticAccessor 。
初始化 。
var builder = WebApplication.CreateBuilder();
builder.Host.UsePinnedScopeServiceProvider(); // 唯一初始化步骤
var app = builder.Build();
app.Run();
页面继承PinnedScopeComponentBase 。
推荐直接在_Imports.razor中声明.
// _Imports.razor
@inherits DependencyInjection.StaticAccessor.Blazor.PinnedScopeComponentBase
与Server端步骤基本一致,只是引用的NuGet有所区别:
安装NuGet 。
启动项目引用DependencyInjection.StaticAccessor.Blazor.WebAssembly 。
dotnet add package DependencyInjection.StaticAccessor.Blazor.WebAssembly 。
非启动项目引用DependencyInjection.StaticAccessor 。
dotnet add package DependencyInjection.StaticAccessor 。
初始化 。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.UsePinnedScopeServiceProvider();
await builder.Build().RunAsync();
页面继承PinnedScopeComponentBase 。
推荐直接在_Imports.razor中声明.
// _Imports.razor
@inherits DependencyInjection.StaticAccessor.Blazor.PinnedScopeComponentBase
你可能会使用其他包定义的ComponentBase基类,由于C#不支持多继承,所以这里提供了不继承PinnedScopeComponentBase的解决方案.
// 假设你现在使用的ComponentBase基类是ThirdPartyComponentBase
// 定义新的基类继承ThirdPartyComponentBase
public class YourComponentBase : ThirdPartyComponentBase, IHandleEvent, IServiceProviderHolder
{
private IServiceProvider _serviceProvider;
[Inject]
public IServiceProvider ServiceProvider
{
get => _serviceProvider;
set
{
PinnedScope.Scope = new FoolScope(value);
_serviceProvider = value;
}
}
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
{
return this.PinnedScopeHandleEventAsync(callback, arg);
}
}
// _Imports.razor
@inherits YourComponentBase
除了PinnedScopeComponentBase,还提供了PinnedScopeOwningComponentBase和PinnedScopeLayoutComponentBase,后续会根据需要可能会加入更多类型。如有需求,也欢迎反馈和提交PR. 。
虽然你可以通过PinnedScope.Scope获取当前的DI Scope,但最好不要通过该属性直接操作IServiceScope对象,比如调用Dispose方法,你应该通过你创建Scope时创建的变量进行操作.
一般日常开发时不需要关注这个问题的,通常的AspNetCore项目也不会出现这样的场景,而Blazor就是官方项目类型中一个非通常DI Scope的案例.
在解释什么是非通常Scope前,我先聊聊通常的Scope模式。我们知道DI Scope是可以嵌套的,在通常情况下,嵌套的Scope呈现的是一种栈的结构,后创建的scope先释放,井然有序.
using (var scope11 = serviceProvider.CreateScope()) // push scope11. [scope11]
{
using (var scope21 = scope11.ServiceProvider.CreateScope()) // push scope21. [scope11, scope21]
{
using (var scope31 = scope21.ServiceProvider.CreateScope()) // push scope31. [scope11, scope21, scope31]
{
} // pop scope31. [scope11, scope21]
using (var scope32 = scope21.ServiceProvider.CreateScope()) // push scope32. [scope11, scope21, scope32]
{
} // pop scope32. [scope11, scope21]
} // pop scope21. [scope11]
using (var scope22 = scope11.ServiceProvider.CreateScope()) // push scope22. [scope11, scope22]
{
} // pop scope22. [scope22]
} // pop scope11. []
了解了非通常Scope,那么就很好理解非通常Scope了,只要是不按照这种井然有序的栈结构的,那就是非通常Scope。比较常见的就是Blazor的这种情况:
我们知道,Blazor SSR通过SignalR实现SPA,一个SignalR连接对应一个DI Scope,界面上的各种事件(点击、获取焦点等)通过SignalR通知服务端回调事件函数,而这个回调便是从外部横插一脚与SignalR进行交互的,在不进行特殊处理的情况下,回调事件所属的Scope是当前回调事件新创建的Scope,但我们在回调事件中与之交互的Component是SignalR所属Scope创建的,这就出现了Scope交叉交互的情况。PinnedScopeComponentBase所做的便是在执行回调函数之前,将PinnedScope.Scope重设回SignalR对应Scope.
正如前面所说,DependencyInjection.StaticAccessor的核心逻辑是从肉夹馍的DI扩展中抽离出来的,抽离后肉夹馍DI扩展将依赖于DependencyInjection.StaticAccessor。现在你可以直接引用DependencyInjection.StaticAccessor,然后直接通过PinnedScope.Scope与DI进行交互,但还是推荐通过肉夹馍DI扩展进行交互,DI扩展提供了一些额外的功能,稍后将一一介绍.
Autofac相关包未发生重大变化,后续介绍的扩展包都是官方DependencyInjection的相关扩展包 。
本次不仅仅是一个简单的代码抽离,代码的核心实现上也有更新,更新后移出了扩展方法CreateResolvableScope,直接支持官方的CreateScope和CreateAsyncScope方法。同时扩展包Rougamo.Extensions.DependencyInjection.AspNetCore和Rougamo.Extensions.DependencyInjection.GenericHost合并为Rougamo.Extensions.DependencyInjection.Microsoft.
仅定义切面类型的项目需要引用Rougamo.Extensions.DependencyInjection.Microsoft,启动项目根据项目类型引用DependencyInjection.StaticAccessor相关包即可,初始化也是仅需要完成DependencyInjection.StaticAccessor初始化即可.
Rougamo.Extensions.DependencyInjection.Microsoft针对MethodContext提供了丰富的DI扩展方法,简化代码编写.
public class TestAttribute : AsyncMoAttribute
{
public override ValueTask OnEntryAsync(MethodContext context)
{
context.GetService<ITestService>();
context.GetRequiredService(typeof(ITestService));
context.GetServices<ITestService>();
}
}
DependencyInjection.StaticAccessor提供的是一种常用场景下获取当前Scope的IServiceProvider解决方案,但在千奇百怪的开发需求中,总会出现一些不寻常的DI Scope场景,比如前面介绍的非通常Scope,再比如Blazor。针对这种场景,肉夹馍DI扩展虽然不能帮你获取到正确的IServiceProvider对象,但如果你自己能够提供获取方式,肉夹馍DI扩展可以方便的集成该获取方式.
下面以Blazor为例,虽然已经针对Blazor特殊的DI Scope提供了通用解决方案,但Blazor还存在着自己的特殊场景。我们知道Blazor SSR服务生存期是整个SignalR的生存期,这个生存期可能非常长,一个生存期期间可能会创建多个页面(ComponentBase),这多个页面也将共享注册为Scoped的对象,这在某些场景下可能会存在问题(比如共享EF DBContext),所以微软提供了OwningComponentBase,它提供了更短的服务生存期,集成该类可以通过ScopedServices属性访问IServiceProvider对象.
// 1. 定义前锋类型,针对OwningComponentBase返回ScopedServices属性
public class OwningComponentScopeForward : SpecificPropertyFoolScopeProvider, IMethodBaseScopeForward
{
public override string PropertyName => "ScopedServices";
}
// 2. 初始化
var builder = WebApplication.CreateBuilder();
// 初始化DependencyInjection.StaticAccessor
builder.Host.UsePinnedScopeServiceProvider();
// 注册前锋类型
builder.Services.AddMethodBaseScopeForward<OwningComponentScopeForward>();
var app = builder.Build();
app.Run();
// 3. 使用
public class TestAttribute : AsyncMoAttribute
{
public override ValueTask OnEntryAsync(MethodContext context)
{
// 当TestAttribute应用到OwningComponentBase子类方法上时,ITestService将从OwningComponentBase.ScopedServices中获取
context.GetService<ITestService>();
}
}
除了上面示例中提供的OwningComponentScopeForward,还有根据字段名称获取的SpecificFieldFoolScopeProvider,根据宿主类型通过lambda表达式获取的TypedFoolScopeProvider<>,这里就不一一举例了,如果你的获取逻辑更加复杂,可以直接实现先锋类型接口IMethodBaseScopeForward.
除了前锋类型接口IMethodBaseScopeForward,还提供了守门员类型接口IMethodBaseScopeGoalie,在调用GetService系列扩展方法时,内部实现按 [先锋类型 -> PinnedScope.Scope.ServiceProvider -> 守门员类型 -> PinnedScope.RootServices] 的顺序尝试获取IServiceProvider对象.
完整示例请访问:https://github.com/inversionhourglass/Rougamo.DI/tree/master/samples 。
最后此篇关于.NET全局静态可访问IServiceProvider(支持Blazor)的文章就讲到这里了,如果你想了解更多关于.NET全局静态可访问IServiceProvider(支持Blazor)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 5 年前。 Improve
在 C# 静态方法中是否有一种方法可以引用定义该方法的类型? 在实例方法中,您可以通过以下方式确定类型: public void Foo() { Type type = this.GetTyp
WPF:静态、动态资源以及资源词典 静态资源与动态资源 我们常常会使用样式或者控件模板放在Window.Resources中,比如这样: 静态资源与动态资源使用如下: <Window
任何人都知道如何在共享/静态函数中动态加载控件?该函数本身位于 mustinherit/abstract 类中。 (这是 VB 中的 ASP.NET 项目)我想做这样的事情: VB: Publ
在我看来,静态/强类型编程语言最宝贵的一点是它有助于重构:如果/当您更改任何 API,那么编译器会告诉您该更改破坏了什么。 我可以想象用运行时/弱类型语言编写代码......但我无法想象没有编译器的帮
正如我的名字所暗示的,我是一名 .NET 开发人员,但我对 Java 的兴趣越来越大,并且我有兴趣学习更多其他语言,因为这有助于我学习更多关于编程的知识。 无论如何,我的问题是:不带参数/不使用状态的
我在java中使用WireMock来 stub POST请求。该请求返回一个存储在我本地的 json 正文文件。 stub 看起来像这样: wireMockServer.stubFor(get(url
Python 是否有类构造函数的机制,即每当首次引用类时(而不是创建该对象的实例时)调用的函数?我知道其他一些语言中也存在这种情况,但我还没有在 Python 中遇到过。 基本上,我想初始化该函数中的
Python 是否有类构造函数的机制,即每当首次引用类时(而不是创建该对象的实例时)调用的函数?我知道其他一些语言中也存在这种情况,但我还没有在 Python 中遇到过。 基本上,我想初始化该函数中的
这个问题已经有答案了: What is the difference between dynamic and static polymorphism in Java? (14 个回答) 已关闭 4 年
这个问题已经有答案了: 已关闭10 年前。 Possible Duplicate: Static initializer in Java 我想知道这个静态的东西(抱歉,这是我第一次遇到这个)对一个类有
如果c++应用程序是按以下方式组织的 //file1.cpp static Y sgObj = X::getInitObject(0); //declared in file scope //fil
我有一个抽象类(AvergedDataRecord),我需要进一步抽象(DataRecord),这样我就可以将它扩展到原始类和一个新的具体类(SummedDataRecord),并且我在获取某些方法时
我正在尝试制作一个字符串枚举。这是我到目前为止所得到的, private class TypedEnum : IEnumerable { public IEnumerator GetEnume
我选修了一门名为“安全代码”的类(class),在下一个作业中,我们应该对一些 C 文件和 JavaEE Web 项目进行静态/动态分析。 我检查了“源监视器”并在 C 文件上运行它,但是(除非我不知
我有两个类,一个是登录类,一个是用户类。在 loggedIn 类中,我想显示我在用户登录时所做的共享首选项。 loginPrefs = getSharedPreferences("loginprefe
我在同一个 Activity 中有两个静态 fragment ,在“fragmentA”中我有一个自定义列表,当一个项目被点击时必须在“fragmentB”中出现一个细节,细节只在我改变屏幕方向时出现
在 Java 中是未修改方法变量,缺少final,每次都重新初始化限定符 静态方法 实例方法 如果 1. 或 2.(或两者)的答案是 final 限定符允许 Java 执行优化并存储方法变量只有一次?
我有两个类相互交互。第一个是中心的,如下: public class Datenbank { double winkelPanel = 0; double groessePanel = 0; doub
我有一个 mysql 数据库,它连接基于 Web 的 php 应用程序和 FoxPro 应用程序(是的,foxpro)。在之前的“开发人员”被解雇后开始处理这个问题。 无论如何,我熟悉 AES_Enc
我是一名优秀的程序员,十分优秀!