preamble
How to access DI containers in static methods has long been an agonizing problem, especially for those who love to write extension methods. The reason why you will suffer for this problem is because of a special service survival - Scoped (Scoped), the so-called Scoped is the scope of the single instance, the most common WebAPI/MVC in a request corresponds to a scope, all registered as Scoped objects in the same request is a single instance of the Scoped If you just use a static field to store the scoped objects that are created when the application is started, theIServiceProvider
object, then it is not possible to correctly get the Scoped object created in the current request through this field in a request.
Earlier in the day there was a release for the Meatpacking (Rougamo) access DI containerSome columns of NuGet, since Meat Loaf can be applied not only to instance methods but also to static methods, the fundamental problem with Meat Loaf accessing DI containers is how to access DI containers in static methods. Considering that accessing DI containers in static methods is a common public problem, the core logic is now abstracted into a series of separate NuGet packages for those who don't use Meat Loaf.
Quick Start
Launch project references
dotnet add package
Non-initiated project references
dotnet add package
// 1. Initialization. This is demonstrated here with a generic host, other types of items will be exemplified later on
var builder = ();
(); // This is the only step that completes the initialization.
var host = (); (); // This is the only step that completes the initialization.
(); // This is the only step in the initialization process.
// 2. Get it anywhere
class Test
{
public static void M()
{
var yourService = <IYourService>();
}
}
As in the example above, the static attributeto get the current Scope's
IServiceProvider
object, which returns the root if it is not currently in any of the scopes.IServiceProvider
。
Imprint
due toThe implementation includes access to non-public members of the official Microsoft package through reflection. The official internal implementation is constantly changing with version iteration, so corresponding versions are released for different versions of the official package.
All NuGet packages use a semantic version number format (SemVer), where the master version number is the same as the
.*
The same, the minor version number is the feature release version number, and the revision number is the bug fixes and minor changes version number. Please install the NuGet package with the same version number as the one you reference..*
The latest version with the same major version number.
It should also be noted that since I can only select .NET8.0 when I create a blazor project locally, the blazor related packages are only available in version 8.0, so if you do have a need for a lower version, you can submit an issue in github.
WebAPI/MVC Initialization Example
Launch project references
dotnet add package
Non-initiated project references
dotnet add package
var builder = ();
(); // The only initialization step
var app = (); // The only initialization step.
(); // The only initialization step.
Blazor Usage Examples
Blazor's DI Scope is a special existence, in WebAssembly mode Scoped is equivalent to a single instance; while in Server mode Scoped corresponds to a SignalR connection. For this special Scope scenario of Blazor, in addition to initialization operations, some additional operations are required.
As we know, Blazor projects can choose the interactive rendering mode when they are created, and all other modes except Server mode create two projects, with the name of this extra project starting with.Client
Ending.Here I call.Client
The project is the Client-side project and the other project is the Server-side project (the only one in Server mode is also called the Server-side project).
Server-side projects
-
Installing NuGet
Launch project references
dotnet add package
Non-initiated project references
dotnet add package
-
initialization
var builder = (); (); // The only initialization step var app = (); // The only initialization step. (); // The only initialization step.
-
Page inheritance
PinnedScopeComponentBase
It is recommended that you add the following information directly to the
_Imports.razor
Statement in.// _Imports.razor @inherits
Client Side Project
The steps are basically the same as on the Server side, except that the referenced NuGet is different:
-
Installing NuGet
Launch project references
dotnet add package
Non-initiated project references
dotnet add package
-
initialization
var builder = (args); (); await ().RunAsync();
-
Page inheritance
PinnedScopeComponentBase
It is recommended that you add the following information directly to the
_Imports.razor
Statement in.// _Imports.razor @inherits
There is already a solution for customizing the ComponentBase base class
You may be able to use other package-definedComponentBase
base class, and since C# does not support multiple inheritance, it is provided here without inheriting thePinnedScopeComponentBase
The solution.
// Assuming you're currently using theComponentBaseThe base class isThirdPartyComponentBase
// Define new base class inheritanceThirdPartyComponentBase
public class YourComponentBase : ThirdPartyComponentBase, IHandleEvent, IServiceProviderHolder
{
private IServiceProvider _serviceProvider;
[Inject]
public IServiceProvider ServiceProvider
{
get => _serviceProvider;
set
{
= new FoolScope(value);
_serviceProvider = value;
}
}
Task (EventCallbackWorkItem callback, object? arg)
{
return (callback, arg);
}
}
// _Imports.razor
@inherits YourComponentBase
Other ComponentBase base classes
apart fromPinnedScopeComponentBase
The company also providesPinnedScopeOwningComponentBase
cap (a poem)PinnedScopeLayoutComponentBase
, more types may be added subsequently as needed. Feedback and PR submissions are also welcome.
caveat
Avoiding direct manipulation of IServiceScope via PinnedScope
While you can passGet the current DI Scope, but it's best not to manipulate it directly through this attribute
IServiceScope
object, such as calling the Dispose method, you should do so through the variable you created when you created the Scope.
Non-usual Scope is not supported
While this is generally not a concern for day-to-day development, and the usual AspNetCore projects don't present such a scenario, Blazor is a case of a non-usual DI Scope in the official project type.
Before explaining what a non-usual Scope is, let me talk about the usual Scope pattern. We know that DI Scope can be nested, in the usual case, the nested Scope presents a kind of stack structure, the later created scope is released first, well organized.
using (var scope11 = ()) // push scope11. [scope11]
{
using (var scope21 = ()) // push scope21. [scope11, scope21]
{
using (var scope31 = ()) // push scope31. [scope11, scope21, scope31]
{
} // pop scope31. [scope11, scope21]
using (var scope32 = ()) // push scope32. [scope11, scope21, scope32]
{
} // pop scope32. [scope11, scope21]
} // pop scope21. [scope11]
using (var scope22 = ()) // push scope22. [scope11, scope22]
{
} // pop scope22. [scope22]
} // pop scope11. []
The most common case is that of Blazor:
As we know, Blazor SSR realizes SPA through SignalR, a SignalR connection corresponds to a DI Scope, various events on the interface (clicking, getting focus, etc.) notify the server-side callback event function through SignalR, and this callback is from the outside of the cross-cutting and SignalR to interact, in the case of no special handling Without special handling, the Scope of the callback event is the newly created Scope of the current callback event, but we interact with theComponent
is created by the Scope to which SignalR belongs, which gives rise to a Scope cross-interaction.PinnedScopeComponentBase
All that is done is that before executing the callback function, theReset back to SignalR corresponding to Scope.
Meat Loaf Related Apps
As stated earlier.The core logic of this is abstracted from the DI extension of Meat Loaf, and after the abstraction the Meat Loaf DI extension will rely on the
. Now you can directly reference the
and then directly through the
Interact with DI, but it is still recommended to do so through the Meatpacking DI extension, which provides some additional features that will be described later.
DI Expansion Pack Changes
Autofac-related packages have not changed significantly, the subsequent introduction of the extension packages are the official DependencyInjection-related extension packages
This time it's not just a simple code extraction, the code has been updated on the core implementation, updated to move out the extension methodsCreateResolvableScope
The official direct support of theCreateScope
cap (a poem)CreateAsyncScope
method. Meanwhile the expansion packcap (a poem)
combine
。
Only items that define a cutout type require a reference to theThe startup project is referenced according to the type of project
The initialization is also just a matter of completing the
Initialization is sufficient.
Easier-to-use extensions
be directed against
MethodContext
Provides rich DI extension methods to simplify code writing.
public class TestAttribute : AsyncMoAttribute
{
public override ValueTask OnEntryAsync(MethodContext context)
{
<ITestService>();
(typeof(ITestService));
<ITestService>();
}
}
Get IServiceProvider from current host type instance
provided is a common scenario to get the current Scope's
IServiceProvider
solutions, but there are always some unusual DI Scope scenarios that arise from the myriad of development requirements, such as the previously describedUnusual Scope, and then Blazor. for this scenario, the Meat Loaf DI extension, while not helping you get the correctIServiceProvider
object, but if you can provide the fetch yourself, the Meatpacking DI extension can easily integrate that fetch.
The following is an example of Blazor, although a generic solution has been provided for Blazor's special DI Scope, Blazor also has its own special scenarios. We know that Blazor SSR Service Survival is the entire SignalR survival period, this survival period may be very long, a survival period may create multiple pages (ComponentBase), this multiple pages will also share the object registered as Scoped, which in some scenarios there may be a problem (such as sharing the EF DBContext), so Microsoft has provided a solution for Blazor's special DI Scope. So Microsoft providesOwningComponentBase, which provides shorter service survival, integrating the class can be done through theScopedServices
Property AccessIServiceProvider
Object.
// 1. Define the type of striker,be directed againstOwningComponentBasecome (or go) backScopedServicescausality
public class OwningComponentScopeForward : SpecificPropertyFoolScopeProvider, IMethodBaseScopeForward
{
public override string PropertyName => "ScopedServices";
}
// 2. initialization
var builder = ();
// initialization
();
// Registered Striker Type
<OwningComponentScopeForward>();
var app = ();
();
// 3. utilization
public class TestAttribute : AsyncMoAttribute
{
public override ValueTask OnEntryAsync(MethodContext context)
{
// (coll.) fail (a student)TestAttributeapply toOwningComponentBaseWhen subclassing methods on the,ITestServicewill take from it
<ITestService>();
}
}
In addition to those provided in the example above, theOwningComponentScopeForward
, and there are also fields that are obtained based on the name of theSpecificFieldFoolScopeProvider
, obtained via lambda expression based on host typeTypedFoolScopeProvider<>
If your acquisition logic is more complex, you can directly implement the pioneer type interfaceIMethodBaseScopeForward
。
In addition to the striker type interfaceIMethodBaseScopeForward
The goalie type interface is also providedIMethodBaseScopeGoalie
TheGetService
When extending the methods in a series, the internal implementation tries to get [Pioneer type -> -> Goalie type -> ] in the order ofIServiceProvider
Object.
Full Example
For a complete example, please visit:/inversionhourglass//tree/master/samples