I Love Stackoverflow
I Love Stackoverflow

Reputation: 6868

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?

Upvotes: 2

Views: 910

Answers (6)

user20059122
user20059122

Reputation:

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.

Upvotes: 0

victor6510
victor6510

Reputation: 1334

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.

Upvotes: 3

Jimenemex
Jimenemex

Reputation: 3166

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";
}

Upvotes: 3

ColinM
ColinM

Reputation: 74

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();
}

Upvotes: 2

jo_Veera
jo_Veera

Reputation: 303

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.

Upvotes: 2

MD. RAKIB HASAN
MD. RAKIB HASAN

Reputation: 3956

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;

Upvotes: 2

Related Questions