How to use Asp Net Core DI & Reflection
Tags: dotnet, csharp, webdev
Published: 2022-02-12 16:08:46

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](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/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.

```csharp
objectType.GetType().AssemblyQualifiedName;
```

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.

```csharp
public object GetReflectedObject(object objectType)
{
    var assemblyQualifiedName = objectType.GetType().AssemblyQualifiedName;
    return Activator.CreateInstance(Type.GetType(assemblyQualifiedName));
}
```

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.

```csharp
public class Factory
{
    private readonly IServiceProvider serviceProvider;

    public Factory(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }
}
```

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.

```csharp
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;
}
```

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.

```csharp
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;
}
```

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

```csharp
/// <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));
    }
}
```

This code example was originally from [Nikcio.UHeadless](https://github.com/nikcio/Nikcio.UHeadless)

[Original file can be found here](https://github.com/nikcio/Nikcio.UHeadless/blob/b978a4539ae2d1d20f252fbe4a78d3ac93dccaf6/src/Nikcio.UHeadless/Reflection/Factories/DependencyReflectorFactory.cs#L1-L116)