I have StrategyName set in appsettings.json which represents the name of the strategy class. I need to get an instance of it.
ITradingStrategy _tradingStrategy = StrategyUtils.GetStrategyInstance(logger, _tradeOptions.StrategyName)
which is equal to
ITradingStrategy _tradingStrategy = new RsiStrategy(logger);
Is it possible to be made in a better way? It works but looks ugly. Since we know the strategy name in the beginning (from appsettings.json), there should probably be a way to obtain it in a better ASP.NET Core way. Maybe some cool extension method, I don't know.
appsettings.json
{
"TradeConfiguration": {
"StrategyName": "RsiStrategy",
...
}
}
Code
public class LiveTradeManager : ITradeManager
{
private readonly ILogger _logger;
private readonly IExchangeClient _exchangeClient;
private readonly ITradingStrategy _tradingStrategy;
private readonly ExchangeOptions _exchangeOptions;
private readonly TradeOptions _tradeOptions;
public LiveTradeManager(ILogger logger, IConfiguration configuration, IExchangeClient exchangeClient)
{
_logger = logger;
_exchangeClient = exchangeClient;
_exchangeOptions = configuration.GetSection("ExchangeConfiguration").Get<ExchangeOptions>();
_tradeOptions = configuration.GetSection("TradeConfiguration").Get<TradeOptions>();
_tradingStrategy = StrategyUtils.GetStrategyInstance(logger, _tradeOptions.StrategyName); // This is the questioned line
}
}
public static ITradingStrategy GetStrategyInstance(ILogger logger, string strategyName)
{
var strategyType = Assembly.GetAssembly(typeof(StrategyBase))
.GetTypes().FirstOrDefault(type => type.IsSubclassOf(typeof(StrategyBase)) && type.Name.Equals(strategyName));
if (strategyType == null)
{
throw new ArgumentException($"The strategy \"{strategyName}\" could not be found.", nameof(strategyName));
}
var strategy = Activator.CreateInstance(strategyType, logger) as ITradingStrategy;
return strategy;
}
// Strategies
public interface ITradingStrategy
{
IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles);
}
public abstract class StrategyBase : ITradingStrategy
{
private readonly ILogger _logger;
protected StrategyBase(ILogger logger)
{
_logger = logger;
}
public abstract IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles);
}
public class RsiStrategy : StrategyBase
{
private readonly ILogger _logger;
public RsiStrategy(ILogger logger) : base(logger)
{
_logger = logger;
}
public override IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles)
{
... _logger.Information("Test");
}
}
// Main
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
})
.ConfigureServices((hostingContext, services) =>
{
services.AddSingleton(
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostingContext.Configuration)
.CreateLogger());
services.AddSingleton<ITradeManager, LiveTradeManager>();
services.AddSingleton<IExchangeClient, BinanceSpotClient>();
services.AddHostedService<LifetimeEventsHostedService>();
})
.UseSerilog();
}
Your problem can be solved multiple ways and using reflection would be the last one.
From your problem statement, I figure that you have multiple strategy classed implementing ITradingStrategy interface, and you configuration value from appsettings.json file decides which strategy to use.
One of the approach you can use here is to use factory to initialize appropriate strategy class based on the configuration value.
Following is the factory class and interface which will create Strategy class object based on the strategy name passed to it.
public interface IStrategyFactory
{
ITradingStrategy GetStrategy(string strategyName);
}
public class StrategyFactory : IStrategyFactory
{
private IServiceProvider _serviceProvider;
public StrategyFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public ITradingStrategy GetStrategy(string strategyName)
{
switch (strategyName)
{
case "Rsi":
// Resolve RsiStrategy object from the serviceProvider.
return _serviceProvider.GetService<RsiStrategy>();
case "Dmi":
// Resolve DmiStrategy object from the serviceProvider.
return _serviceProvider.GetService<DmiStrategy>();
default:
return null;
}
}
}
This strategy can now be used in controller and call its GetStrategy method by passing the strategy name which in-turn is retrieved from the configuration.
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
// Strategy factory.
private IStrategyFactory _strategyFactory;
// Configuration
private IConfiguration _configuration;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration, IStrategyFactory strategyFactory)
{
_logger = logger;
_strategyFactory = strategyFactory;
_configuration = configuration;
}
public IActionResult Index()
{
// Get Configuration value "StrategyName" from configuration.
// In your case this will be your own custom configuration.
var strategyName = _configuration.GetValue<string>("StrategyName");
// Pass strategyName to GetStrategy Method.
var strategy = _strategyFactory.GetStrategy(strategyName);
// Call Prepare method on the retrieved strategy object.
ViewBag.PreparedList = strategy.Prepare(new List<OHLCV>());
return View();
}
}
For the above code to work you need to register strategy classed in to serviceCollection.
services.AddSingleton<RsiStrategy>();
services.AddSingleton<DmiStrategy>();
And also the StrategyFactory.
services.AddSingleton<IStrategyFactory, StrategyFactory>();
EDIT
Based on your comment below, you need to be able to resolve the strategy types without additional overhead of registering them in DI as when new types are created and also without making changes in the factory.
You need to use reflection for this. Using reflection you can determine the types which you want to register in the DI. As following.
//Get all the types which are inheriting from StrategyBase class from the assembly.
var strategyTypes = Assembly.GetAssembly(typeof(StrategyBase))
?.GetTypes()
.Where(type => type.IsSubclassOf(typeof(StrategyBase)));
if (strategyTypes != null)
{
//Loop thru the types collection and register them in serviceCollection.
foreach (var type in strategyTypes)
{
services.Add(new ServiceDescriptor(typeof(StrategyBase), type, ServiceLifetime.Singleton));
}
}
With the above code, all the types which are inheriting from StrategyBase are registered in serviceCollection. Now using serivceProvider we can get all the registered instances and look for the instance which has correct strategyName.
So the factory's GetStrategy method will look like as following.
public ITradingStrategy GetStrategy(string strategyName)
{
var strategies = _serviceProvider.GetServices<StrategyBase>();
var strategy = strategies.FirstOrDefault(s => s.GetType().Name == strategyName);
if (strategy == null)
{
throw new ArgumentException($"The strategy \"{strategyName}\" could not be found.", nameof(strategyName));
}
return strategy;
}
I hope this will help you resolve your issue.
Related
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?
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";
}
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:
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.
Type.GetType() is used and you might remove the public Type GetInstance() method.
Other codes remain the same as yours.
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;
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.
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();
}
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.
I have often code that requires dynamic initialization after the program has started and looks something like that (WinFoms):
public class MyParentComponent : IMyParentComponent
{
private readonly ILogger<MyParentComponent> _logger;
private readonly IMyChildComponentFactory _factory;
private readonly Dictionary<int, IMyChildComponent> _children;
public MyParentComponent(ILogger<MyParentComponent> logger, IMyChildComponentFactory factory)
{
_logger = logger;
_factory = factory;
_children = new Dictionary<int, IMyChildComponent>();
}
// WinForms, called with parameter from GUI.
public void Initialize(int instanceCnt)
{
for (var id = 0; id < instanceCnt; id++)
{
var child = _factory.CreateComponent(id);
_children.Add(id, child);
}
}
}
public interface IMyParentComponent
{
void Initialize(int instanceCnt);
}
public class MyChildComponent : IMyChildComponent
{
private readonly ILogger<MyChildComponent> _logger;
private int _id;
public MyChildComponent(ILogger<MyChildComponent> logger, int id)
{
_logger = logger;
_id = id;
}
}
public interface IMyChildComponent
{
}
For that, I usually create factory manually and use it to resolve dependency by some kind of id (no pre-defined list).
I use IServiceProvider instead of ILifetimeScope to skip dependency to Autofac and think that components should have no dependency on DI contained and don't want to inject ILifetimeScope in MyParentComponent ctor.
public interface IMyChildComponentFactory
{
IMyChildComponent CreateComponent(int id);
}
/// <summary>
/// Factory to create components.
/// </summary>
public class MyChildComponentFactory : IMyChildComponentFactory
{
private readonly ILogger<MyChildComponentFactory> _logger;
private readonly IServiceProvider _serviceProvider;
public MyChildComponentFactory(ILogger<MyChildComponentFactory> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
public IMyChildComponent CreateComponent(int id)
{
var logger = _serviceProvider.GetRequiredService<ILogger<MyChildComponent>>();
var mInstance = new MyChildComponent(logger, id);
return mInstance;
}
}
In Autofac, I can use delegate factories for that, but in some cases I need to resolve other dependencies like ILogger and that makes MyParentComponent dependent on DI container.
Is there any pre-defined pattern for that?
Abstract Class:
public abstract class Rater
{
public Rater()
{
}
public abstract decimal Rate(Policy policy);
}
Child classes:
public class AutoPolicyRater : Rater
{
public readonly ILogger<AutoPolicyRater> _logger;
public AutoPolicyRater(ILogger<AutoPolicyRater> logger)
{
_logger = logger;
}
public override decimal Rate(Policy policy)
{
_logger.Log("Rating AUTO policy...");
_logger.Log("Validating policy.");
if (String.IsNullOrEmpty(policy.Make))
{
_logger.Log("Auto policy must specify Make");
return 0m;
}
if (policy.Make == "BMW")
{
if (policy.Deductible < 500)
{
return 1000m;
}
return 900m;
}
return 0m;
}
}
public class LandPolicyRater : Rater
{
public readonly ILogger<LandPolicyRater> _logger;
public AutoPolicyRater(ILogger<LandPolicyRater> logger)
{
_logger = logger;
}
public override decimal Rate(Policy policy)
{
_logger.Log("Rating LAND policy...");
_logger.Log("Validating policy.");
if (policy.BondAmount == 0 || policy.Valuation == 0)
{
_logger.Log("Land policy must specify Bond Amount and Valuation.");
return 0m;
}
if (policy.BondAmount < 0.8m * policy.Valuation)
{
_logger.Log("Insufficient bond amount.");
return 0m;
}
return (policy.BondAmount * 0.05m);
}
}
Factory class, where I want to dynamically pass the logger object:
public class RaterFactory
{
private readonly IRatingUpdater _ratingUpdater;
public RaterFactory(ILogger logger)
{
}
public Rater Create(Policy policy)
{
try
{
return (Rater)Activator.CreateInstance(
Type.GetType($"MyCompany.{policy.Type}PolicyRater"),
new object[] { ?? });//here I want to pass logger object
}
catch
{
return new UnknownPolicyRater();
}
}
}
As these classes are not controllers, and I want to create object in my factory method, how can I pass logger object and log information to application insight? I would like to pass generic logger object, however, if there is another approach to achieve, I'm ok.
EDIT:
After #fildor's suggestion, I tried below and it is logging information in Application Insight traces.
public class RaterFactory
{
private readonly ILoggerFactory _loggerFactory;
public RaterFactory(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public Rater Create(Policy policy)
{
try
{
string typeString = $"MyCompany.{policy.Type}PolicyRater";
ILogger modelLogger = _loggerFactory.CreateLogger(typeString);
return (Rater)Activator.CreateInstance(
Type.GetType($"MyCompany.{policy.Type}PolicyRater"),
new object[] { modelLogger });
}
catch
{
return new UnknownPolicyRater();
}
}
}
public class AutoPolicyRater : Rater
{
public readonly ILogger _logger;
public AutoPolicyRater(ILogger logger)
{
_logger = logger;
}
//other code
}
As requested: a possible implementation:
public class RaterFactory
{
private readonly ILoggerFactory _loggerFactory;
public RaterFactory(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
public Rater Create(Policy policy)
{
try
{
return (Rater)Activator.CreateInstance(
Type.GetType($"MyCompany.{policy.Type}PolicyRater"),
new object[] { _loggerFactory });
}
catch
{
return new UnknownPolicyRater();
}
}
}
And then ...
public class AutoPolicyRater : Rater
{
private readonly ILogger<AutoPolicyRater> _logger;
public AutoPolicyRater(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AutoPolicyRater>();
}
public override decimal Rate(Policy policy)
{
// ... ommited for brevity
}
}
The RaterFactory class has no need to know in advance all dependencies injected into the instances it creates.
Instead, you can inject IServiceProvider and let ActivatorUtilities resolve the dependencies of the Rater instances that you are creating each time.
This is how it can be done:
public class RaterFactory
{
private readonly IServiceProvider _serviceProvider;
public RaterFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Rater Create(Policy policy)
{
try
{
// OPTION 1
return (Rater)ActivatorUtilities.CreateInstance(
_serviceProvider,
Type.GetType($"MyCompany.{policy.Type}PolicyRater"));
// OPTION 2
return (Rater)ActivatorUtilities.GetServiceOrCreateInstance(
_serviceProvider,
Type.GetType($"MyCompany.{policy.Type}PolicyRater"));
}
catch
{
return new UnknownPolicyRater();
}
}
}
As shown above, there are two possible options that you should choose according to your needs and constraints.
ActivatorUtilities.CreateInstance: This method creates each time a new instance and does not query the service collection for the target type. This is convenient if you don't know all the possible target types in advance (or you don't want to register them for some reason).
ActivatorUtilities.GetServiceOrCreateInstance: This method looks for the target type into the service collection; if a registration is found, it returns the corresponding instance, otherwise it behaves like ActivatorUtilities.CreateInstance. This means that you can register the target type in the service collection as usual with the most appropriate lifetime (singleton, scoped or transient) for each type. The only downside of this approach is that, if you have some singleton or scoped target types, you have to provide a way to register them in the service collection, which may be tricky in a plugin-like application.
Again, please note that there are no constraints on which dependencies can be injected in the Rater subtypes, because after all the "dirty" work of dependency resolution is done by the ActivatorUtilities class.
I am using Autofac for IoC
Here is my container initiator class, which the responsibility is to register the dependencies.
public class ContainerInit
{
public static IContainer BuildContainer()
{
var conFac = new ContainerFactory();
var builder = new ContainerBuilder();
builder.Register(conFac).As<IContainerFactory>().SingleInstance();
builder.Register(c=> new MainClass(conFac)).As<IMainClass>().SingleInstance();
builder.Register(c=> new Database(conFac)).As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
ContainerFactory.SetContainer(container);
return container;
}
}
Problem with this approach is, I need to pass IContainerFactory to the constructor of every class I use in my application as follow
public class MainClass: IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(IContainerFactory containerFactory)
{
_logger = containerFactory.GetInstance<ILogger>();
_db = containerFactory.GetInstance<IDatabase>(); //example
}
public AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
So it is difficult to unit test these classes.
How can come up with a good solution?
A better approach would be to pass the dependencies you need in your class into your constructor:
public class MainClass : IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(ILogger logger, IDatabase db)
{
_logger = logger;
_db = db;
}
public void AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
Then you could use a mocking framework such as Moq to mock your class dependencies and perform verifications on whether the dependencies were called:
[TestClass]
public class UnitTest1
{
private Mock<ILogger> _mockLogger = new Mock<ILogger>();
private Mock<IDatabase> _mockDb = new Mock<IDatabase>();
[TestMethod]
public void TestMethod1()
{
// arrange
var mainClass = new MainClass(_mockLogger.Object, _mockDb.Object);
var data = new Data();
// act
mainClass.AddDetails(data);
// assert
_mockDb
.Verify(v => v.Add(data), Times.Once);
}
}
I would not verify your log message though as this could change and make the test brittle. Only verify functionality which is essential to doing what the method is intended for.
Your current Service Locator Anti-Pattern is what makes your code difficult to test in isolation as well as makes the class misleading about what it actually depends on.
MainClass should be refactored to follow Explicit Dependencies Principle
public class MainClass : IMainClass
private readonly ILogger logger;
private readonly IDatabase db;
public MainClass(ILogger logger, IDatabase db) {
this.logger = logger;
this.db = db;
}
public void AddDetails(Data data) {
//do some business operations
db.Add(data);
logger.Information("added");
}
}
The same pattern should also be followed for any other class you have that depends on the container factory, like Database.
You would however need to also refactor the container registration accordingly
public class ContainerInit {
public static IContainer BuildContainer() {
var builder = new ContainerBuilder();
builder.RegisterType<MainClass>().As<IMainClass>().SingleInstance();
builder.RegisterType<Database>().As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
return container;
}
}
Testing MainClass would required you to mock only the necessary dependencies of the class under test.
[TestClass]
public class MainClassTests {
[TestMethod]
public void Should_AddDetails_To_Database() {
// Arrange
var mockDb = new Mock<IDatabase>();
var data = new Data();
var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);
// Act
mainClass.AddDetails(data);
// Assert
mockDb.Verify(_ => _.Add(data), Times.Once);
}
}
Here I would like to share solution, which I use in my project
To do unit testing of particular function, I use below structure
[TestClass]
public class TestSomeFunction
{
public IComponentContext ComponentContext { get; set; }
[TestInitialize]
public void Initialize()
{
//Registering all dependencies required for unit testing
this.ComponentContext = builder.Build(); //You have not build your container in your question
}
[TestMethod]
public void Testfunction()
{
//Resolve perticular dependency
var _logger = containerFactory.Resolve<ILogger>();
//Test my function
//use _logger
}
}
I have an Address object that depends on an IState object inject in the constructor:
public class AddressService : BaseService, IAddressService
{
private readonly IStateRepository _stateRepository;
private readonly ICacheClass _cache;
private readonly ILogger<AddressService> _logger;
public const string StatesCacheKey = "StateListManagedByStateService";
public AddressService(IStateRepository stateRepository,
ICacheClass cache,
ILogger<AddressService> logger,
IUserContext userContext) : base(userContext)
{
_stateRepository = stateRepository;
_cache = cache;
_logger = logger;
}
public async Task<IReadOnlyList<T>> GetAllStatesAsync<T>() where T : IState
{
var list = await _cache.GetAsync<IReadOnlyList<T>>(StatesCacheKey);
if (list != null) return list;
var repoList = _stateRepository.GetAll().Cast<T>().ToList();
_logger.LogInformation("GetAllStates retrieved from repository, not in cache.");
await _cache.SetAsync(StatesCacheKey, repoList);
return repoList;
}
}
IStateRepository is injected by the ASP.NET Core web project with the folling lines in Startup:
services.AddTransient<IStateRepository, Local.StateRepository>();
services.AddTransient<IAddressService, AddressService>();
In my Controller I have two action methods. Depending on the one called, I want to change the IStateRepository object associated with DI:
private readonly IAddressService _addressService;
public HomeController(IConfiguration configuration, ILogger<HomeController> logger, IAddressService addressService) : base(configuration,logger)
{
_addressService = addressService;
}
public async Task<IActionResult> DistributedCacheAsync()
{
var services = new ServiceCollection();
services.Replace(ServiceDescriptor.Transient<IStateRepository, Repositories.Local.StateRepository>());
ViewBag.StateList = await _addressService.GetAllStatesAsync<State>();
return View();
}
public async Task<IActionResult> DapperAsync()
{
var services = new ServiceCollection();
services.Replace(ServiceDescriptor.Transient<IStateRepository, Repositories.Dapper.StateRepository>());
ViewBag.StateList = await _addressService.GetAllStatesAsync<State>();
return View();
}
The problem is that _addressService already has the Local.StateRepository associated from Startup, and updating it in DapperAsync using Replace doesn't have an effect on it.
How would be able to change IStateRepository in DI during runtime in the example above?
Changing services at runtime is not supported by Microsoft.Extensions.DependencyInjection.
I recommend creating a wrapper service that at runtime can choose between different implementations based on a condition.