简体   繁体   中英

Register .NET Core singleton service via annotation

Is there any way to autowire (automatically register) singleton services to a C# DI container ( Microsoft.Extensions.DependencyInjection ) by annotation?

Eg something like ProvidedIn option in @Injectable() annotation in Angular , @injectable() annotation in InversifyJS (Node.js) , autowiring in Spring (Java) or autowiring in Symfony Framework (PHP) ?

See Angular example below:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

At this time I have to add each singleton service to ServiceCollection manually this way (and forget to do it sometimes):

internal static ServiceProvider SetupDi()
{
    return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
        .AddDbContext<DbContext>()
        .AddSingleton<ServiceA>()
        .AddSingleton<ServiceB>();
}

The desired equivalent solution would be something like this:

internal static ServiceProvider SetupDi()
{
    return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
        .AddDbContext<DbContext>();
}

[Injectable()]
public class ServiceA
{
}

[Injectable()]
public class ServiceB
{
}

No, and that's actually by-design.

The whole point of DI is that there are no surprises with how your program is configured: everything you configure in ConfigureServices (your in your case, SetupDi ) is exactly what you get at runtime. By using attributes to configure DI then that would introduce "non-local effects" and it would be much harder to track-down bugs introduced by incorrect or misconfigured dependencies caused by an errant attribute.

(To that extent, I disagree with Angular's design - but that's off-topic).

(I also feel that .NET Core's DI system is also imperfect - too many necessary details are hidden behind DI injection extension-methods that you need to use ILSpy or Reflector to peer into).

As a workaround, you can "test" your DI services at app startup to ensure everything is configured by reflecting over every IService in your project and trying to instantiate the implementation.

Here's the code I use in my ASP.NET and ASP.NET Core projects to verify DI is thoroughly configured:

internal static void TestAllServices( IServiceProvider sp )
{
    Assembly[] mySolutionAssemblies = new[]
    {
        typeof(FromAssemblyX.Foobar).Assemby,
        typeof(FromAssemblyY.Foobar).Assemby,
        typeof(FromAssemblyZ.Foobar).Assemby,
    };

    List<Type> allServiceInterfaceTypes = mySolutionAssemblies
        .SelectMany( ass => ass.GetTypes() )
        .Where( t => t.IsInterface && t.IsPublic )
        .Where( /* Filter out interfaces you don't want to verify here */ )
        .ToList();

    foreach( Type serviceInterfaceType in serviceInterfaceTypes )
    {
        try
        {
            Object implementation = sp.GetRequiredService( serviceInterfaceType );
            if( implementation is IDisposable disp ) disp.Dispose();
        }
        catch( Exception ex )
        {
            // Log an error or throw or set a breakpoint here
        }
    }
}

Notes:

  • Don't pass in your "real" IServiceProvider into TestAllServices - instead create a separate IServiceProvider instance, because this method will dispose any implementation that implements IDisposable , even if they're singletons.
  • I recommend having this code wrapped in #if DEBUG to ensure it doesn't go into production.
  • A major downside to .NET Core's default DI system is that it's impossible to statically differentiate "scoped" service implementations from singletons and transient services - you could work-around this by adding marker-interfaces on the service implementations and handling those accordingly.

Another note:

If you really wanted to, you could use the same allServiceInterfaceTypes technique above inside your ConfigureServices method to enumerate over all interface and detect all types implementing each interface, and discover any custom attributes on those types and automatically register them in the DI container - but I don't recommend doing that because reflection is slower than using DI as-intended - and configuring things like factory-methods would be much harder.

You can use Scrutor to add assembly scanning capabilities to the ASP.NET Core DI container.

For details you can check this blog https://andrewlock.net/using-scrutor-to-automatically-register-your-services-with-the-asp-net-core-di-container/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM