How to use Asp Net Core DI & Reflection

nikcio

Nikolaj Brask-Nielsen

Posted on February 12, 2022

How to use Asp Net Core DI & Reflection

When using Asp Net Core, you have access to dependency injection in many cases, but how do you go about creating objects in your code using the same dependency injection?

What is reflection?

To start, we have to know what reflection is and how to use it. Reflection is used to create objects dynamically and can be used to create objects in a generic use case.

You can, for example, create an object from the assembly qualified name, which can be found on any type like this.

objectType.GetType().AssemblyQualifiedName;
Enter fullscreen mode Exit fullscreen mode

When doing reflection you will be using the Activator object. To create an object from the assembly qualified name you found above, you would use code like this.

public object GetReflectedObject(object objectType)
{
    var assemblyQualifiedName = objectType.GetType().AssemblyQualifiedName;
    return Activator.CreateInstance(Type.GetType(assemblyQualifiedName));
}
Enter fullscreen mode Exit fullscreen mode

I take out the assembly qualified name because I want to show how to decouple the creation logic from any type of object. This could make it possible to store the assembly qualified names and first later create the objects.

Adding DI to reflection

To add dependency injection you will need to use the IServiceProvider, which is used to get dependencies from the Asp Net Core IOC container. This can be done with a simple constructor injection like this.

public class Factory
{
    private readonly IServiceProvider serviceProvider;

    public Factory(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then to add dependency injection we first have to locate a constructor before we can create the object instance with Activator.CreateInstance.

To find the constructor in this example, we require a specific object type to be the first in the constructor. Then we choose the first that matches that description. This can be done like so.

public void GetConstructor(object objectType)
{
    var type = objectType.GetType();
    var constructors = type.GetConstructors();
    var constructor = constructors.FirstOrDefault(
            constructor => GetFirstParameter(constructor) == typeof(Factory));
}

private static Type GetFirstParameter(ConstructorInfo constructor)
{
    return constructor.GetParameters().FirstOrDefault().ParameterType;
}
Enter fullscreen mode Exit fullscreen mode

We can then check for any other parameters and get them with the service provider and then we have created Asp Net Core DI & Reflection.

public object GetReflectedObject(object objectType)
{
    var requiredFactoryObject = new Factory(serviceProvider);

    var assemblyQualifiedName = objectType.GetType().AssemblyQualifiedName;

    var parameters = GetConstructorParameters(objectType);

    var injectedParamerters = (new object[] { requiredFactoryObject })
        .Concat(GetDIParamters(parameters)).ToArray();

    return Activator.CreateInstance(Type.GetType(assemblyQualifiedName), injectedParamerters);
}

private IEnumerable<object> GetDIParamters(ParameterInfo[] parameters)
{
    return parameters.Skip(1)
        .Select(parameter => serviceProvider.GetService(parameter.ParameterType));
}

public ParameterInfo[] GetConstructorParameters(object objectType)
{
    var type = objectType.GetType();
    var constructors = type.GetConstructors();
    return constructors.FirstOrDefault(
            constructor => GetFirstParameter(constructor) == typeof(Factory))
        .GetParameters();
}

private static Type GetFirstParameter(ConstructorInfo constructor)
{
    return constructor.GetParameters().FirstOrDefault().ParameterType;
}
Enter fullscreen mode Exit fullscreen mode

And with some extra refactoring, you can get this generic factory that can then create an object with a required constructor parameter.

/// <summary>
/// A factory that can create objects with DI
/// </summary>
public class DependencyReflectorFactory
{
    private readonly IServiceProvider serviceProvider;
    private readonly ILogger<DependencyReflectorFactory> logger;

    public DependencyReflectorFactory(IServiceProvider serviceProvider, ILogger<DependencyReflectorFactory> logger)
    {
        this.serviceProvider = serviceProvider;
        this.logger = logger;
    }

    /// <summary>
    /// Gets the reflected type with DI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="typeToReflect">The type to create</param>
    /// <param name="constructorRequiredParamerters">The required parameters on the constructor</param>
    /// <returns></returns>
    public T GetReflectedType<T>(Type typeToReflect, object[] constructorRequiredParamerters)
        where T : class
    {
        var propertyTypeAssemblyQualifiedName = typeToReflect.AssemblyQualifiedName;
        var constructors = typeToReflect.GetConstructors();
        if (constructors.Length == 0)
        {
            LogConstructorError(typeToReflect, constructorRequiredParamerters);
            return null;
        }
        var parameters = GetConstructor(constructors, constructorRequiredParamerters)?.GetParameters();
        if (parameters == null)
        {
            LogConstructorError(typeToReflect, constructorRequiredParamerters);
            return null;
        }
        object[] injectedParamerters = null;
        if (constructorRequiredParamerters == null)
        {
            injectedParamerters = parameters.Select(parameter => serviceProvider.GetService(parameter.ParameterType)).ToArray();
        }
        else
        {
            injectedParamerters = constructorRequiredParamerters
            .Concat(parameters.Skip(constructorRequiredParamerters.Length).Select(parameter => serviceProvider.GetService(parameter.ParameterType)))
            .ToArray();
        }
        return (T)Activator.CreateInstance(Type.GetType(propertyTypeAssemblyQualifiedName), injectedParamerters);
    }

    /// <summary>
    /// Logs a constructor error
    /// </summary>
    /// <param name="typeToReflect"></param>
    /// <param name="constructorRequiredParamerters"></param>
    private void LogConstructorError(Type typeToReflect, object[] constructorRequiredParamerters)
    {
        string constructorNames = string.Join(", ", constructorRequiredParamerters?.Select(item => item.GetType().Name));
        string message = $"Unable to create instance of {typeToReflect.Name}. " +
            $"Could not find a constructor with {constructorNames} as first argument(s)";
        logger.LogError(message);
    }

    /// <summary>
    /// Takes the required paramters from a constructor
    /// </summary>
    /// <param name="constructor"></param>
    /// <param name="constructorRequiredParamertersLength"></param>
    /// <returns></returns>
    private ParameterInfo[] TakeConstructorRequiredParamters(ConstructorInfo constructor, int constructorRequiredParamertersLength)
    {
        var parameters = constructor.GetParameters();
        if (parameters.Length < constructorRequiredParamertersLength)
        {
            return parameters;
        }
        return parameters?.Take(constructorRequiredParamertersLength).ToArray();
    }

    /// <summary>
    /// Validates the required parameters from a constructor
    /// </summary>
    /// <param name="constructor"></param>
    /// <param name="constructorRequiredParameters"></param>
    /// <returns></returns>
    private bool ValidateConstructorRequiredParameters(ConstructorInfo constructor, object[] constructorRequiredParameters)
    {
        if (constructorRequiredParameters == null)
        {
            return true;
        }
        var parameters = TakeConstructorRequiredParamters(constructor, constructorRequiredParameters.Length);
        for (int i = 0; i < parameters.Length; i++)
        {
            var requiredParameter = constructorRequiredParameters[i].GetType();
            if (parameters[i].ParameterType != requiredParameter)
            {
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// Gets a constructor
    /// </summary>
    /// <param name="constructors"></param>
    /// <param name="constructorRequiredParameters"></param>
    /// <returns></returns>
    private ConstructorInfo GetConstructor(ConstructorInfo[] constructors, object[] constructorRequiredParameters)
    {
        return constructors?.FirstOrDefault(constructor =>
            ValidateConstructorRequiredParameters(constructor, constructorRequiredParameters));
    }
}
Enter fullscreen mode Exit fullscreen mode

This code example was originally from Nikcio.UHeadless

Original file can be found here

đź’– đź’Ş đź™… đźš©
nikcio
Nikolaj Brask-Nielsen

Posted on February 12, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related