Asp Net Core DI & Reflection の使い方


Asp Net Core を使用すると、多くの場合、依存性注入にアクセスできますが、同じ依存性注入を使用してコード内にオブジェクトを作成するにはどうすればよいでしょうか?

反射とは?



まず、リフレクションとは何か、そしてその使い方を知る必要があります.リフレクションは、オブジェクトを動的に作成するために使用され、一般的なユースケースでオブジェクトを作成するために使用できます.

たとえば、アセンブリ修飾名からオブジェクトを作成できます.これは、このような任意の型で見つけることができます.

objectType.GetType().AssemblyQualifiedName;


リフレクションを行うときは、Activator オブジェクトを使用します.上で見つけたアセンブリ修飾名からオブジェクトを作成するには、次のようなコードを使用します.

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


作成ロジックを任意のタイプのオブジェクトから分離する方法を示したいので、アセンブリ修飾名を削除します.これにより、アセンブリの修飾名を保存し、後でオブジェクトを作成することが可能になります.

リフレクションに DI を追加する



依存性注入を追加するには、Asp Net Core IOC コンテナーから依存関係を取得するために使用される IServiceProvider を使用する必要があります.これは、このような単純なコンストラクター インジェクションで実行できます.

public class Factory
{
    private readonly IServiceProvider serviceProvider;

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


次に、依存性注入を追加するには、まずコンストラクターを見つけてから、 Activator.CreateInstance でオブジェクト インスタンスを作成する必要があります.

この例でコンストラクターを見つけるには、特定のオブジェクト型をコンストラクターの最初にする必要があります.次に、その説明に一致する最初のものを選択します.これはそのように行うことができます.

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


次に、他のパラメーターを確認してサービス プロバイダーから取得し、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;
}


さらにリファクタリングを追加することで、必要なコンストラクター パラメーターを持つオブジェクトを作成できるこのジェネリック ファクトリを取得できます.

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


このコード例はもともと Nikcio.UHeadless のものです

Original file can be found here