1

I am now using .NET 6 Worker Service to develop an backend service, and I have multi background services need to be registered by AddHostedService<> like:

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<WorkerA>();
        services.AddHostedService<WorkerB>();
        services.AddHostedService<WorkerC>();
    })
    .Build();

await host.RunAsync();

I want to find a better way to register background services dynamically, for example:

var workers = Assembly.GetExecutingAssembly().GetTypes()
    .Where(worker =>
        worker is {IsClass: true, IsAbstract: false} &&
        !string.IsNullOrWhiteSpace(worker.Namespace) &&
        worker.Namespace.Equals(typeof(BaseWorker).Namespace))
    .ToArray();

foreach (var worker in workers)
{
    // Incorrect here
    services.AddHostedService<worker>();
}

However I failed since the worker is a Type not a class name. Could someone give me some instructions, thank you.

Kit
  • 20,354
  • 4
  • 60
  • 103
Ocean Sun
  • 81
  • 6

2 Answers2

2

It is possible. You need to make a generic method for each type and call it. First get the open generic method:

var method = 
    // for the static extensions class where AddHostedService is defined
    typeof(ServiceCollectionHostedServiceExtensions)
    // get the AddHostedService<>() method
    .GetMethod(
        "AddHostedService",
        // that has one generic argument
        1,
        // that is static and public
        BindingFlags.Static | BindingFlags.Public,
        // using the default binder
        null,
        // that takes one IServiceCollection as a parameter
        new Type[] { typeof(IServiceCollection },
        null));

Then change your foreach such that it creates a closed generic version of the method with MakeGenericMethod, and invokes it.

foreach (var worker in workers)
{
    // if worker is a MyWorker, then the below code is
    // equivalent to services.AddHostedService<MyWorker>()

    method
        .MakeGenericMethod(new Type[] { worker })
        .Invoke(null, new object?[] { services });
}

This allows you to call the ServiceCollectionHostedServiceExtensions.AddHostedService method via reflection.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • Hi Kit, thank you so much, I achieved it with you instructions and some changes. I copy the whole logic below. – Ocean Sun Mar 17 '23 at 06:07
  • Welcome. I guess I got it close. I was not in front of a compiler. I will update my answer in case people look at it before yours. – Kit Mar 17 '23 at 14:10
2

Thanks to Kit's instruction, I achieved it with some changes, here is the logic:

/// <summary>
/// Adds the background services under specific namespace.
/// </summary>
/// <param name="services">The instance IServiceCollection.</param>
/// <param name="backgroundServiceNamespace">The namespace of background service.</param>
/// <returns>The configured service.</returns>
public static IServiceCollection AddBackgroundServices(
        this IServiceCollection services,
        string? backgroundServiceNamespace = null)
{
    // Gets method info from static extensions class where ddHostedService<T> is defined.
    var addHostedService = typeof(ServiceCollectionHostedServiceExtensions).GetMethod(
        // To indicate the name of the method.
        name: nameof(ServiceCollectionHostedServiceExtensions.AddHostedService),
        // To indicate the target method has one generic argument.
        genericParameterCount: 1,
        // To indicate the target method is static and public.
        bindingAttr: BindingFlags.Static |
                     BindingFlags.Public,
        // To indicate using the default binder.
        binder: null,
        // To indicate the target method takes IServiceCollection as parameter.
        types: new[] { typeof(IServiceCollection) },
        modifiers: null);

    // Gets all background services under the namespace (if specified).
    var backgroundServiceTypes = Assembly.GetExecutingAssembly().GetTypes()
        .Where(type =>
            type is { IsClass: true, IsAbstract: false } &&
            IsMicrosoftExtensionsHostingBackgroundService(type) &&
            (string.IsNullOrWhiteSpace(backgroundServiceNamespace) ||
             backgroundServiceNamespace.Equals(type.Namespace)))
        .ToArray();

    foreach (var backgroundServiceType in backgroundServiceTypes)
    {
        // The first parameter is null since it is a static method.
        addHostedService!.MakeGenericMethod(backgroundServiceType)
            .Invoke(null, new object?[] { services });
    }

    return services;
}

/// <summary>
/// Checks whether the type is Microsoft.Extensions.Hosting.BackgroundService.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>True means current class inherit from Microsoft.Extensions.Hosting.BackgroundService.</returns>
private static bool IsMicrosoftExtensionsHostingBackgroundService(Type type)
{
    var backgroundServiceFullName = typeof(Microsoft.Extensions.Hosting.BackgroundService).FullName!;
    var baseType = type.BaseType;

    while (baseType != null)
    {
        if (backgroundServiceFullName.Equals(baseType.FullName))
        {
            return true;
        }

        baseType = baseType.BaseType;
    }

    return false;
}
Ocean Sun
  • 81
  • 6