Home > front end >  In C#, How can I dynamically pass T into services.AddHostedService<T>()?
In C#, How can I dynamically pass T into services.AddHostedService<T>()?

Time:09-17

I have a webservice with multiple hosted services.

I want to be able to turn them on and off through "appSettings.json".

In "StartUp.cs", I populate an instance of

    public class HostedServiceSettings
    {
        public StateOfUse BuildMontageTasks { get; init; }
        public StateOfUse BuildTasksFromFileCreations { get; init; }
        public StateOfUse BusinessEventPuller { get; init; }
        public StateOfUse FieldServiceAppointmentChanges { get; init; }
        public StateOfUse MvtBriefingCleanupLoop { get; init; }
        public StateOfUse SfCacheFileListener { get; init; }
        public StateOfUse TaskWorker { get; init; }
    }

StateOfUse is this enum:

    public enum StateOfUse
    {
        Inactive,
        Active
    }

and each property corresponds to a hosted service.

Back in the StartUp class, I've added the following method:

        private void AddHostedServices(IServiceCollection services)
        {
            HostedServiceSettings settings = Configure<HostedServiceSettings>(services, "HostedServices");
            foreach (PropertyInfo property in typeof(HostedServiceSettings).GetProperties())
            {
                StateOfUse stateOfUse = (StateOfUse)property.GetValue(settings);
                if (stateOfUse.IsActive())
                {
                    string hostedServiceName = $"PraxedoIntegration.{property.Name}HostedService";
                    Type hostedServiceType = Type.GetType(hostedServiceName);

                    Type type = typeof(IServiceCollection);
                    MethodInfo methodInfo = type.GetMethod("IServiceCollection.AddHostedService");
                    MethodInfo genericMethod = methodInfo.MakeGenericMethod(hostedServiceType);
                    genericMethod.Invoke(services, null);
                }
            }
        }

This fails because MethodInfo methodInfo resolves to null and thus an exception will be thrown in the next line. The method IServiceCollection.AddHostedService<THostedService>() is a static method inculded in "Microsoft.Extensions.Hosting.Abstractions" in the Microsoft.Extensions.DependencyInjection namespace in public static class ServiceCollectionHostedServiceExtensions. (I don't use nameof in the code because that is not supported for extension methods. :-( )

Is it possible to access this method through generics? If so, how?

CodePudding user response:

One option is to use the overload of AddHostedService which takes an implementationFactory registering each service, but if inactive returns a null implementation

services.AddHostedService<ServiceA>( (services => {
     HostedServiceSettings settings = Configure<HostedServiceSettings>(services, "HostedServices");
     if(settings.IsServiceAActive())
         return new ServiceA();
     return NullService();      
})

CodePudding user response:

Figured out I could solve the problem most simply by wrapping the extension method in a local method, like:

        private void AddHostedServices(IServiceCollection services)
        {
            HostedServiceSettings settings = Configure<HostedServiceSettings>(services, "HostedServices");
            foreach (PropertyInfo property in typeof(HostedServiceSettings).GetProperties())
            {
                StateOfUse stateOfUse = (StateOfUse)property.GetValue(settings);
                if (stateOfUse.IsActive())
                {
                    string hostedServiceName = $"PraxedoIntegration.{property.Name}HostedService";
                    Type hostedServiceType = Type.GetType(hostedServiceName);

                    Type type = typeof(Startup);
                    MethodInfo methodInfo = type.GetMethod("AddHostedService");
                    MethodInfo genericMethod = methodInfo.MakeGenericMethod(hostedServiceType);
                    genericMethod.Invoke(this, new object[] { services });
                }
            }
        }

        // Needed as a work-arround because we can't call the extension method with reflection.
        public IServiceCollection AddHostedService<THostedService>(IServiceCollection services)
            where THostedService : class, IHostedService =>
                services.AddHostedService<THostedService>();

Alternative, it's been drawn to my attention, in this instance, the implementation details for AddHostedService() are known and it is simply a wrapper (arguably "syntactic sugar") for AddTransient(), so I could avoid the reflection by doing this:

        private void AddHostedServices(IServiceCollection services)
        {
            HostedServiceSettings settings = Configure<HostedServiceSettings>(services, "HostedServices");
            foreach (PropertyInfo property in typeof(HostedServiceSettings).GetProperties())
            {
                StateOfUse stateOfUse = (StateOfUse)property.GetValue(settings);
                if (stateOfUse.IsActive())
                {
                    string hostedServiceName = $"PraxedoIntegration.{property.Name}HostedService";
                    Type hostedServiceType = Type.GetType(hostedServiceName);

                    services.AddTransient(typeof(IHostedService), hostedServiceType);
                }
            }
        }
  • Related