Reputation: 6868
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
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
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:
"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.Type.GetType()
is used and you might remove the public Type GetInstance()
method.Other codes remain the same as yours.
Upvotes: 3
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
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
Reputation: 303
I think the problem is on AppDomain.CurrentDomain.GetAssemblies() you could try for the following options -
To enumerate all assemblies that the app is composed from, look at Microsoft.Extensions.DependencyModel. E.g. foreach (var l in Microsoft.Extensions.DependencyModel.DependencyContext.Default.RuntimeLibraries) https://github.com/dotnet/runtime/issues/9184
https://dotnetcoretutorials.com/2020/07/03/getting-assemblies-is-harder-than-you-think-in-c/
https://www.michael-whelan.net/replacing-appdomain-in-dotnet-core/
I think the above links might solve your problem.
Upvotes: 2
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