DependencyInjection.StaticAccessor.Blazor.WebAssembly
8.1.0
See the version list below for details.
dotnet add package DependencyInjection.StaticAccessor.Blazor.WebAssembly --version 8.1.0
NuGet\Install-Package DependencyInjection.StaticAccessor.Blazor.WebAssembly -Version 8.1.0
<PackageReference Include="DependencyInjection.StaticAccessor.Blazor.WebAssembly" Version="8.1.0" />
paket add DependencyInjection.StaticAccessor.Blazor.WebAssembly --version 8.1.0
#r "nuget: DependencyInjection.StaticAccessor.Blazor.WebAssembly, 8.1.0"
// Install DependencyInjection.StaticAccessor.Blazor.WebAssembly as a Cake Addin #addin nuget:?package=DependencyInjection.StaticAccessor.Blazor.WebAssembly&version=8.1.0 // Install DependencyInjection.StaticAccessor.Blazor.WebAssembly as a Cake Tool #tool nuget:?package=DependencyInjection.StaticAccessor.Blazor.WebAssembly&version=8.1.0
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.
Why Do We Need PinnedScope
When working with dependency injection, a common challenge is accessing the IoC/DI container within static methods. There are many solutions to this problem, and the simplest is to save the root container to a static variable during application startup and access it directly afterward:
public static IServiceProvider ServiceProvider;
static void Main(string[] args)
{
var builder = Host.CreateDefaultBuilder();
var host = builder.Build();
ServiceProvider = host.Services;
host.Run();
}
This approach is straightforward and effective for simple scenarios. However, we know that Microsoft Dependency Injection offers three service lifetimes: Transient
, Scoped
, and Singleton
. Among them, Scoped
is particularly special. In web development, a Scoped
service corresponds to the lifetime of a single request. For services registered with the Scoped
lifetime, we cannot retrieve them directly from the root container; they must be accessed through a scope-specific container.
For this requirement in web applications, there is an easy solution. Microsoft provides the IHttpContextAccessor
, which allows us to obtain the current request's IServiceProvider
via IHttpContextAccessor.HttpContext.RequestServices
after retrieving the IHttpContextAccessor
object from the root container.
Why Is PinnedScope Necessary
Despite the existence of these straightforward solutions, scope management becomes more complex in advanced scenarios. For instance, in web applications, you might need to launch a background thread to complete some asynchronous operations, allowing the request to proceed without waiting for the operation to finish. In such asynchronous operations, DI containers are often required. How can we access the DI container in a static method in this case? Can we continue using the root container or IHttpContextAccessor
? The answer is no, because by this point, the request may have already been completed, and accessing IServiceProvider
through IHttpContextAccessor
will result in an exception, as it may reference a disposed service provider.
In such cases, you usually need to create a new scope specifically for the asynchronous operation. While you can manage these scopes manually, PinnedScope
offers a simpler alternative.
The above is just one example. In reality, scope management can be even more complicated in scenarios like task scheduling, message subscriptions, and even Microsoft's official Blazor framework. While you could find tailored solutions for each scenario to access the IServiceProvider
, PinnedScope
provides a unified and simple approach. With PinnedScope
, you don't need to worry about how your framework creates and manages scopes—you can always retrieve the correct IServiceProvider
effortlessly.
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 project discussed here includes Server, WebAssembly, and Auto modes, not just the Server mode specifically.
Install NuGet Package
Run the following command to install the required package:
dotnet add package DependencyInjection.StaticAccessor.Blazor
The initialization process for Blazor Server is similar to that of an AspNetCore project. Refer to the AspNetCore Project Initialization Example for detailed instructions. However, since Blazor's DI scope differs from the standard AspNetCore DI scope, additional steps are required.
Pages Inherit from PinnedScopeComponentBase
Blazor's unique DI scope requires that all pages inherit from PinnedScopeComponentBase
. It is recommended to define this inheritance globally in the _Imports.razor
file so that it applies to all pages.
// _Imports.razor
@inherits DependencyInjection.StaticAccessor.Blazor.PinnedScopeComponentBase
In addition to PinnedScopeComponentBase
, the library provides PinnedScopeOwningComponentBase
and PinnedScopeLayoutComponentBase
, with the possibility of adding more types as needed in the future.
It is important to note that starting from version 8.1, by default, after inheriting PinnedScopeOwningComponentBase, the objects accessed via PinnedScope.ScopedServices in the callback methods of the page and subsequent invoked methods will be the same as those in OwningComponentBase.ScopedServices. This behavior is more in line with expectations, whereas in version 8.0, the objects retrieved were identical to those injected via IServiceProvider.
Solution for Existing Custom ComponentBase Base Classes
If you are already using a custom ComponentBase
base class provided by another package, C#'s lack of multiple inheritance can pose a challenge. Here are the recommended solutions:
If you can modify your base class, and it directly inherits
ComponentBase
,OwningComponentBase
, orLayoutComponentBase
Replace the base class with
PinnedScopeComponentBase
,PinnedScopeOwningComponentBase
, orPinnedScopeLayoutComponentBase
, depending on your use case.If you can modify your base class, but it does not directly inherit from
ComponentBase
,OwningComponentBase
, orLayoutComponentBase
Modify your base class to implement the
IHandleEvent
andIServiceProviderHolder
interfaces. Implement the interface methods based on the correspondingPinnedScope
base class implementations.Unable to modify your base class
Create a new custom base class that inherits from your existing base class, implements the
IHandleEvent
andIServiceProviderHolder
interfaces and implements the required methods by following the correspondingPinnedScope
base class implementations.
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 | Versions 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. |
-
net8.0
- DependencyInjection.StaticAccessor (>= 8.1.0)
- DependencyInjection.StaticAccessor.Blazor (>= 8.1.0)
- Microsoft.AspNetCore.Components.WebAssembly (>= 8.0.0 && < 9.0.0)
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 |
---|---|---|
9.0.0 | 99 | 12/14/2024 |
9.0.0-preview-1733580617 | 70 | 12/7/2024 |
8.1.0 | 92 | 12/13/2024 |
8.1.0-preview-1734072808 | 65 | 12/13/2024 |
8.0.0 | 133 | 9/18/2024 |
8.0.0-preview-1726694007 | 80 | 9/18/2024 |
8.0.0-preview-1726647274 | 75 | 9/18/2024 |
8.0.0-preview-1726214654 | 84 | 9/13/2024 |
8.0.0-preview-1726080008 | 88 | 9/11/2024 |
# 8.1
1. **修改了`PinnedScope.ScopedServices`在`PinnedScopeOwningComponentBase`子类页面事件回调方法及其后续调用方法中的默认表现。在 8.0 版本中,`PinnedScope.ScopedServices`获取到的是与 inject 注入的`IServiceProvider`相同,这一行为与预期不符。在 8.1 及后续版本中,`PinnedScope.ScopedServices`获取到的是`OwningComponentBase.ScopedServies`,这一行为更符合预期。**
2. .NET 7.0 新增`HostApplicationBuilder`,但由于无法从`HostApplicationBuilder`对象获取`IHostBuilder`对象,所以对为`HostApplicationBuilder`新增扩展方法`UsePinnedScopeServiceProvider`和`UseEditableServiceProvider`。
3. 使用`UnsafeAccessorAttribute`替代反射访问`ComponentBase.HandleEventAsync`,提升访问性能。
4. 为了实现第一项描述的功能,修改了`PinnedScopeComponentBase`, `PinnedScopeOwningComponentBase`和`PinnedScopeLayoutComponentBase`的实现,如果之前按照 [已有自定义ComponentBase基类的解决方案](https://github.com/inversionhourglass/DependencyInjection.StaticAccessor/blob/blazor-v8.0.0/README.md#%E5%B7%B2%E6%9C%89%E8%87%AA%E5%AE%9A%E4%B9%89componentbase%E5%9F%BA%E7%B1%BB%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88) 修改了`ComponentBase`系列基类,现在你需要重新参照最新的 [已有自定义ComponentBase基类的解决方案](https://github.com/inversionhourglass/DependencyInjection.StaticAccessor/blob/66294298181b1bf81e3d4e8cbfb10f352ca889e3/README.md#%E5%B7%B2%E6%9C%89%E8%87%AA%E5%AE%9A%E4%B9%89componentbase%E5%9F%BA%E7%B1%BB%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88) 进行修改。