DependencyInjection.StaticAccessor.Blazor.WebAssembly 8.0.0-preview-1726694007

This is a prerelease version of DependencyInjection.StaticAccessor.Blazor.WebAssembly.
There is a newer version of this package available.
See the version list below for details.
dotnet add package DependencyInjection.StaticAccessor.Blazor.WebAssembly --version 8.0.0-preview-1726694007                
NuGet\Install-Package DependencyInjection.StaticAccessor.Blazor.WebAssembly -Version 8.0.0-preview-1726694007                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DependencyInjection.StaticAccessor.Blazor.WebAssembly" Version="8.0.0-preview-1726694007" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add DependencyInjection.StaticAccessor.Blazor.WebAssembly --version 8.0.0-preview-1726694007                
#r "nuget: DependencyInjection.StaticAccessor.Blazor.WebAssembly, 8.0.0-preview-1726694007"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install DependencyInjection.StaticAccessor.Blazor.WebAssembly as a Cake Addin
#addin nuget:?package=DependencyInjection.StaticAccessor.Blazor.WebAssembly&version=8.0.0-preview-1726694007&prerelease

// Install DependencyInjection.StaticAccessor.Blazor.WebAssembly as a Cake Tool
#tool nuget:?package=DependencyInjection.StaticAccessor.Blazor.WebAssembly&version=8.0.0-preview-1726694007&prerelease                

DependencyInjection.StaticAccessor

中文 | English

DependencyInjection.StaticAccessor is dedicated to providing static access to the IServiceProvider object corresponding to the current DI Scope for various types of .NET projects. This allows you to easily use IServiceProvider in static methods and in types where direct interaction with DI services is not possible.

NuGet Packages Overview

Package Name Purpose
DependencyInjection.StaticAccessor.Hosting For AspNetCore projects (WebApi, Mvc, etc.) and Generic Host
DependencyInjection.StaticAccessor.Blazor For Blazor projects, including Blazor Server and Blazor WebAssembly Server
DependencyInjection.StaticAccessor.Blazor.WebAssembly For Blazor WebAssembly Client projects, also supports Auto mode for Client projects
DependencyInjection.StaticAccessor Base library, A non-startup project using PinnedScope references the package

Version Number Explanation

All version numbers follow the Semantic Versioning format. The major version matches the version of Microsoft.Extensions.* ( please make sure the major version matches the Microsoft.Extensions.* version you are using when referencing the NuGet package). The minor version indicates feature updates, while the patch version is for bug fixes.

Quick Start

Install the corresponding NuGet package based on your project type, as listed in the NuGet Packages Overview.

// 1. Initialization (Generic Host)
var builder = Host.CreateDefaultBuilder();

builder.UsePinnedScopeServiceProvider(); // Only this step to complete initialization

var host = builder.Build();

host.Run();

// 2. Access anywhere
class Test
{
    public static void M()
    {
        var yourService = PinnedScope.ScopedServices.GetService<IYourService>();
    }
}

The initialization method is similar for different project types, and all require calling the UsePinnedScopeServiceProvider extension method. Example initialization code for different project types will be provided later.

AspNetCore Project Initialization Example

Install the NuGet package

dotnet add package DependencyInjection.StaticAccessor.Hosting

// 1. Initialization
var builder = WebApplication.CreateBuilder();

builder.Host.UsePinnedScopeServiceProvider(); // Just this step to complete the initialization

var app = builder.Build();

app.Run();

Blazor Server-Side Project Initialization

Note: The Blazor Server-side here includes Server, WebAssembly, and Auto modes of the Server-side project, not just the Server mode specifically.

Install the NuGet package

dotnet add package DependencyInjection.StaticAccessor.Blazor

The initialization process for Blazor Server-side is the same as AspNetCore. Please refer to the AspNetCore Project Initialization Example. However, since Blazor's DI scope is different from the conventional one, additional steps are required.

Inherit PinnedScopeComponentBase for Pages

Due to Blazor's unique DI scope, all pages need to inherit from PinnedScopeComponentBase. It is recommended to define this once in _Imports.razor so that it applies to all pages.

// _Imports.razor

@inherits DependencyInjection.StaticAccessor.Blazor.PinnedScopeComponentBase

In addition to PinnedScopeComponentBase, PinnedScopeOwningComponentBase and PinnedScopeLayoutComponentBase are also provided, and more types may be added as needed.

Solution for Existing Custom ComponentBase Base Classes

You might be using a ComponentBase base class defined by another package, and since C# does not support multiple inheritance, here is a solution that does not require inheriting from PinnedScopeComponentBase.

// Assuming your current ComponentBase base class is ThirdPartyComponentBase

// Define a new base class that inherits from 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

Blazor WebAssembly Client Initialization

Note: This is for Blazor WebAssembly Client-side initialization. For Server-side initialization, please refer to Blazor Server-Side Project Initialization

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.UsePinnedScopeServiceProvider(); // Just this step to complete the initialization

await builder.Build().RunAsync();

Similar to the Server-side, Client-side pages also need to inherit from PinnedScopeComponentBase. Please refer to Inherit PinnedScopeComponentBase for Pages.

Notes

Do Not Operate IServiceScope Through PinnedScope

Although you can access the current DI Scope via PinnedScope.Scope, please do not directly manipulate PinnedScope.Scope, such as calling the Dispose method. Instead, you should operate through the variable created when you initially created the scope.

Does Not Support Non-Standard Scopes

In typical development scenarios, this issue usually does not need to be addressed, and it generally does not occur in standard AspNetCore projects. However, Blazor is an example of a non-standard DI Scope in official project types.

Before explaining what a non-standard Scope is, let's first talk about the standard Scope mode. We know that DI Scopes can be nested, and under normal circumstances, the nested Scopes form a stack structure, where the most recently created Scope is released first, in an orderly manner.

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.  [scope11]
}                                                                      // pop scope11.  []

With this understanding of standard Scopes, non-standard Scopes can be easily understood. Any Scope that does not follow this orderly stack structure is considered a non-standard Scope. A common example is the scenario with Blazor:

As we know, Blazor SSR implements SPA through SignalR, where each SignalR connection corresponds to a DI Scope. Various events on the interface (clicks, focus acquisition, etc.) notify the server to callback event functions via SignalR. These callbacks are inserted externally to interact with SignalR. Without special handling, the Scope belonging to the callback event is a newly created Scope for that event. However, the Component we interact with in the callback event is created within the Scope belonging to SignalR, resulting in cross-Scope interaction. PinnedScopeComponentBase addresses this by resetting PinnedScope.Scope to the SignalR corresponding Scope before executing the callback function.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
8.0.0 105 9/18/2024
8.0.0-preview-1726694007 74 9/18/2024
8.0.0-preview-1726647274 71 9/18/2024
8.0.0-preview-1726214654 80 9/13/2024
8.0.0-preview-1726080008 82 9/11/2024

# DependencyInjection.StaticAccessor

中文 | [English](README_en.md)

`DependencyInjection.StaticAccessor`致力于为.NET各种类型的项目提供静态方式访问当前DI Scope对应的`IServiceProvider`对象,你可以很方便的在静态方法以及无法直接与DI服务交互的类型的方法中使用`IServiceProvider`。

## NuGet包一览

|                          包名                         |                               用途                              |
|:-----------------------------------------------------:|:---------------------------------------------------------------|
| DependencyInjection.StaticAccessor.Hosting            | 用于AspNetCore项目(WebApi、Mvc等)及通用主机(Generic Host)     |
| DependencyInjection.StaticAccessor.Blazor             | 用于Blazor项目,Blazor Server和Blazor WebAssembly Server都用这个 |
| DependencyInjection.StaticAccessor.Blazor.WebAssembly | 用于Blazor WebAssembly Client项目,同样支持Auto模式Client项目     |
| DependencyInjection.StaticAccessor                    | 基础类库,一般不直接引用                                         |

### 版本号说明

所有版本号格式都采用语义版本号(SemVer),主版本号与`Microsoft.Extensions.*`保持一致(**引用NuGet时请保持主版本号与你引用的`Microsoft.Extensions.*`保持一致**),次版本号作为功能更新版本号,修订号为bug修复及微小改动版本号。

## 快速开始

根据项目类型,参考[nuget包一览](#nuget包一览)安装对应NuGet。

```csharp
// 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>();
   }
}
```

不同类型的项目初始化方式类似,都是调用扩展方法`UsePinnedScopeServiceProvider`,后面会给出不同类型项目初始化的示例代码。

### AspNetCore项目初始化示例

安装NuGet引用
> dotnet add package DependencyInjection.StaticAccessor.Hosting

```csharp
// 1. 初始化
var builder = WebApplication.CreateBuilder();

builder.Host.UsePinnedScopeServiceProvider(); // 仅此一步完成初始化

var app = builder.Build();

app.Run();
```

### Blazor Server端项目初始化

**注意,这里的Blazor Server端是包含Server, WebAssembly, Auto三种模式的Server端项目,不是单指Server模式。**

安装NuGet引用
> dotnet add package DependencyInjection.StaticAccessor.Blazor

Blazor Server端初始化操作与AspNetCore相同,直接参考[AspNetCore项目初始化示例](#aspnetcore项目初始化示例),这里不再赘述。不过由于Blazor的DI scope与常规的不同,所以还需要做一些额外的操作。

#### 页面继承PinnedScopeComponentBase

Blazor的特殊DI scope需要所有页面需要继承自`PinnedScopeComponentBase`,推荐做法是在`_Imports.razor`直接定义,一次定义所有页面都生效。

```csharp
// _Imports.razor

@inherits DependencyInjection.StaticAccessor.Blazor.PinnedScopeComponentBase

```

除了`PinnedScopeComponentBase`,还提供了`PinnedScopeOwningComponentBase`和`PinnedScopeLayoutComponentBase`,后续会根据需要可能会加入更多类型。

#### 已有自定义ComponentBase基类的解决方案

你可能会使用其他包定义的`ComponentBase`基类,C#不支持多继承,所以这里提供了不继承`PinnedScopeComponentBase`的解决方案。

```csharp
// 假设你现在使用的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
```

### Blazor WebAssembly Client初始化

**注意,这里是Blazor WebAssembly Client端的初始化,Server端的初始化请查看[Blazor Server端项目初始化](#blazor-server端项目初始化)**

```csharp
var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.UsePinnedScopeServiceProvider(); // 仅此一步完成初始化

await builder.Build().RunAsync();
```

与Server端相同,Client端的页面也需要继承`PinnedScopeComponentBase`,请参考[页面继承PinnedScopeComponentBase](#页面继承pinnedscopecomponentbase)。

## 注意事项

### 不要通过PinnedScope操作IServiceScope

虽然你可以通过`PinnedScope.Scope`获取当前的DI Scope,但请不要通过该属性直接操作`PinnedScope.Scope`,比如调用`Dispose`方法,你应该通过你创建scope时创建的变量进行操作。

### 不支持非通常Scope

一般日常开发时不需要关注这个问题的,通常的AspNetCore项目也不会出现这样的场景,Blazor就是官方项目类型中非通常DI Scope的案例。

在解释什么是非通常Scope前,我先聊聊通常的Scope模式。我们知道DI Scope是可以嵌套的,在通常情况下,嵌套的Scope呈现的是一种栈的结构,后创建的scope先释放,井然有序。

```csharp
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。

---