Location>code7788 >text

NET Global Static Accessible IServiceProvider (Blazor support)

Popularity:773 ℃/2024-09-19 11:16:13

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, theIServiceProviderobject, 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'sIServiceProviderobject, 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.ClientEnding.Here I call.ClientThe 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

  1. Installing NuGet

    Launch project references

    dotnet add package

    Non-initiated project references

    dotnet add package

  2. initialization

    var builder = ();
    
    (); // The only initialization step
    
    var app = (); // The only initialization step.
    
    (); // The only initialization step.
    
  3. Page inheritancePinnedScopeComponentBase

    It is recommended that you add the following information directly to the_Imports.razorStatement 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:

  1. Installing NuGet

    Launch project references

    dotnet add package

    Non-initiated project references

    dotnet add package

  2. initialization

    var builder = (args);
    
    ();
    
    await ().RunAsync();
    
  3. Page inheritancePinnedScopeComponentBase

    It is recommended that you add the following information directly to the_Imports.razorStatement in.

    // _Imports.razor
    
    @inherits 
    

There is already a solution for customizing the ComponentBase base class

You may be able to use other package-definedComponentBasebase class, and since C# does not support multiple inheritance, it is provided here without inheriting thePinnedScopeComponentBaseThe 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 fromPinnedScopeComponentBaseThe company also providesPinnedScopeOwningComponentBasecap (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 attributeIServiceScopeobject, 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 theComponentis created by the Scope to which SignalR belongs, which gives rise to a Scope cross-interaction.PinnedScopeComponentBaseAll 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 theand then directly through theInteract 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 methodsCreateResolvableScopeThe official direct support of theCreateScopecap (a poem)CreateAsyncScopemethod. 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 projectThe initialization is also just a matter of completing theInitialization is sufficient.

Easier-to-use extensions

be directed againstMethodContextProvides 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'sIServiceProvidersolutions, 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 correctIServiceProviderobject, 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 theScopedServicesProperty AccessIServiceProviderObject.

// 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 interfaceIMethodBaseScopeForwardThe goalie type interface is also providedIMethodBaseScopeGoalieTheGetServiceWhen extending the methods in a series, the internal implementation tries to get [Pioneer type -> -> Goalie type -> ] in the order ofIServiceProviderObject.

Full Example

For a complete example, please visit:/inversionhourglass//tree/master/samples