Home > Software design >  How to pass logger object while creating instance using reflection?
How to pass logger object while creating instance using reflection?

Time:05-04

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
}

CodePudding user response:

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

CodePudding user response:

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.

  1. 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).
  2. 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.

  • Related