Search code examples
c#.net-corereflection

Getting null when trying to create instance of class at run time in .NET Core


I am trying to create an instance of a class at runtime and call the method but it's not working for me.

I have a class library where I have a class and method as below :

Class library MyApp.Service:

namespace MyApp.Service.SchedularOperations
{
    public class WeeklyTaskSchedular : ISchedular
    {
        private readonly IDbConnection _dbConnection;
        private readonly IProductService _productService;
        
        public WeeklyTaskSchedular(IDbConnection dbConnection,IProductService productService)
        {
            _dbConnection = dbConnection;
            _productService = productService;
             Frequency = Frequencies.Weekly
        }

        public Frequencies Frequency { get ; set ; }

        public int Process (ScheduleInfo info)
        {
            //process logic 
            return 0; indicate success
        }
    }
}

BackgroundService project:

namespace MyApp.BackgroundSchedularService
{
    public class RunSchedular : BackgroundService
    {
        private readonly ILogger<RunSchedular> _logger;
        private readonly IProductService _productService;

        public RunSchedular(ILogger<RunSchedular> logger, IProductService productService)
        {
            _logger = logger;
            _productService = productService;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string connectionString = GetThirdPartyConnectionStringFromDatabase();
                string executionClassName = "MyApp.Service.SchedularOperations.WeeklyTaskSchedular"; //coming from database
                IDbConnection connection = new SqlConnection(connectionString);
                object[] constructorArgs = { connection, _productService};
                
                Type type = GetInstance(executionClassName); //getting null
                object instance = Activator.CreateInstance(type,constructorArgs);
                
                object[] methodArgs = { new ScheduleInfo() };
                
                type.GetMethod("Process").Invoke(instance,methodArgs);
                await Task.Delay(1000, stoppingToken);
            }
        }
        
        public Type GetInstance(string strFullyQualifiedName)
        {
            foreach(var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                Type type = asm.GetType(strFullyQualifiedName);
                if(type !=null)
                    return type;
            }
            return null;
        }
    }
}

public class Program
{
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<RunSchedular>();
                    services.AddTransient<IProductService, ProductService>();
                });
}

Problem is this :

Type type = GetInstance(executionClassName); //getting null

Can someone please help me create an instance of a class at run time with constructor arguments and call the method?


Solution

  • The approach seems wrong to me, you should try to use DI to help with this.

    Register all the schedulers as services appropriately:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddTransient<IDbConnection, DbConnection>();
    
                // Notice here that these schedulars are singletons throughout their execution since they will be created under the singleton hosted service RunSchedular.
                services.AddTransient<ISchedular, WeeklyTaskSchedular>();
                services.AddTransient<ISchedular, DailyTaskSchedular>(); // Made an assumption here this exists also
    
                services.AddHostedService<RunSchedular>();
            });
    

    Then inject the framework provided IServiceProvider and get the service based on the type matching.

    public class RunSchedular : BackgroundService
    {
        private readonly IDbConnection _dbConnection;
        private readonly IServiceProvider _serviceProvider;
    
        public RunSchedular(IDbConnection dbConnection, IServiceProvider provider)
        {
            _dbConnection = dbConnection;
            _serviceProvider = provider;
        }
    
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string executionClassName = _dbConnection.GetSchedularType();
                ISchedular schedular = _serviceProvider.GetServices<ISchedular>().First(x => x.GetType() == Type.GetType(executionClassName));
                schedular.Process(new ScheduleInfo());
                await Task.Delay(1000, stoppingToken);
            }
        }
    

    DBConnection.cs

    public string GetSchedularType()
    {
        // Imagine data access stuff
        return "MyApp.Service.SchedularOperations.WeeklyTaskSchedular";
    }