2

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?

8
  • If your type was in DI, this would be a whole lot easier. Commented Oct 7, 2022 at 16:31
  • @gunr2171 How can I register with DI when the implementation name of class is stored in the database and I need to call different implementation(Weekly,Monthly etc) at run time? Commented Oct 7, 2022 at 16:33
  • 1
    You can vastly simplify this question. Most of the code you've included has nothing to do with the behavior you're seeing. Clearly the problem is that GetType("MyApp.Service.SchedularOperations.WeeklyTaskSchedular") is returning null for all the assemblies in your domain. Maybe try logging the assemblies you're searching to see if the class library assembly hasn't been loaded? If that assembly is loaded, try logging all the types it has declared, and see if your type is in that list? Commented Oct 7, 2022 at 16:41
  • FYI : learn.microsoft.com/en-us/dotnet/api/… Commented Oct 7, 2022 at 16:43
  • 2
    Assemblies won't be included if code referring to them hasn't been accessed IIRC: stackoverflow.com/questions/10284861/… Commented Oct 7, 2022 at 16:49

6 Answers 6

3
+100

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";
}
Sign up to request clarification or add additional context in comments.

1 Comment

I like the approach of using DI to create the instance. But the shown code creates instances of all implementing types, chooses one and discards the others. Check this answer for factory implementation example when only the required type is created: stackoverflow.com/a/73640633/2557855
3

I assume that the MyApp.Service class library is referenced in your BackgroundService project (since you are able to create an instance of the ScheduleInfo class). In this case I would suggest you use the Type.GetType() instead of walking through loaded assemblies which need some additional steps to ensure that it is loaded before you need it.

Here is the modified RunScheduler:

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, MyApp.Service"; //coming from database
                IDbConnection connection = new SqlConnection(connectionString);
                object[] constructorArgs = { connection, _productService };

                Type type = Type.GetType(executionClassName);
                object instance = Activator.CreateInstance(type, constructorArgs);

                object[] methodArgs = { new ScheduleInfo() };

                type.GetMethod("Process").Invoke(instance, methodArgs);
                await Task.Delay(1000, stoppingToken);
            }
        }
    }
}

Two key changes:

  1. The class name is "MyApp.Service.SchedularOperations.WeeklyTaskSchedular, MyApp.Service" which specifies the assembly (dll) name. Although a fully qualified name shall include version, culture and public key token, this should do for your case.
  2. Type.GetType() is used and you might remove the public Type GetInstance() method.

Other codes remain the same as yours.

2 Comments

Would like to get the instance from DI and not use Activator.CreateInstance. Still upvoted for your kind efforts towards helping me
@ILoveStackoverflow Your example code also uses Activator.CreateInstance and you only ask for a solution to get the type, since your code returns null at that point. Thus I think this answer should be marked as correct, if the solution works. If you want to solve it with DI that should be another question.
2

Fully qualified class name is case sensitive. Your class name "MyApp.Service.SchedularOperations.WeeklyTaskSchedular"; is not match with physical namespaces.

Check fully qualified class name:

string fullyQualifiedName = typeof(WeeklyTaskSchedular).AssemblyQualifiedName;

4 Comments

I want to create instance of class based on provider name stored in the database. Don’t want to hardcode the logic to create instance of class
WeeklyTaskSchedular is this class exist on your project??
It exists in "MyApp.Service" class library
check again my answer, your class name is not correct.
2

I think the problem is on AppDomain.CurrentDomain.GetAssemblies() you could try for the following options -

I think the above links might solve your problem.

Comments

2

Inject ISchedularFactory into your RunSchedular class.
Then that can read the chosen schedule from db and return the concrete implementation, e.g. WeeklySchedular/MonthlySchedular

public class SchedularFactory
{
    ISchedular GetSchedular()
    {
        var dbData = "monthly";
        return dbData switch
        {
            "monthly" => new MonthlySchedular(),
            "daily" => new DailySchedular(),
        };
    }
}

public class MonthlySchedular : ISchedular
{
    public void Execute()
    {
        // do monthly tasks
    }
}

public class DailySchedular : ISchedular
{
    public void Execute()
    {
        // do daily tasks
    }
}

public interface ISchedular
{
    public void Execute();
}

2 Comments

Can you share any sample code implementation of how to do it please?
Added example code
0

Your methos is correct. I'm also using that logic types kept in database and creating them at runtime with reflection because of some specific requirements. I think you don't have the necessary assembly.In our design we also keep path data in database to necessary assemblies and search/load from those paths.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.