3

We are migrating a project from .Net Framework 4.6.2 to .Net Core 2.0. I have a situation where a single interface is implemented by multiple classes, all these implementations are required to be injected into consuming client and be executed in some order (here the order and class name is picked from configuration at runtime). I am injecting IServiceProvider to resolve these implementations in the consuming class constructor, but getting StackOverflowException while building the objects.

The caveat here is all these classes implement a single interface, and their instances must be injected into consuming class. The default DI container provided by .Net Core 2.0 does not support named(class name) or typed(class type) injection for multiple classes implementing same interface, as supported by other DI options like Unity(our previous container).

To solve this I am injecting IServiceProvider and wrapping it up in another class which builds all instances and then filters the required instance based on the class type or class name.

However, when I try to build all these instances, the runtime throws StackOverFlowException.

Note - All the classes are registered with Transient Scope, and creating individual interface for each class and injecting them is not an option.

My IServiceProvider wrapper -

public class TypedImplementationDependencyInjector
{
    private readonly IServiceProvider _serviceProvider = null;

    public TypedImplementationDependencyInjector(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public T ResolveImplementation<U, T>() where T : class, U
    {
        if (typeof(U).IsInterface)
        {
            var services = _serviceProvider.GetServices<U>();
            var typedImplementation = services.FirstOrDefault(o => o.GetType() == typeof(T)) as T;
            return typedImplementation;
        }

        return default(T);
    }

public T ResolveImplementation<T>(Type type)
    {
        if (type == null)
            throw new TypeLoadException("Supplied type was null or empty");

        if (typeof(T).IsInterface)
        {   
            var services = _serviceProvider.GetServices<T>();
            var typedImplementation = services.FirstOrDefault(o => o.GetType() == type);
            return typedImplementation;
        }

        return default(T);
    }

    public T ResolveImplementation<T>(string assemblyName, string namespacename, string classname)
    {
        if (typeof(T).IsInterface)
        {
            Type type = Type.GetType($"{namespacename}.{classname}, {assemblyName}", true, true);
            return ResolveImplementation<T>(type);
        }

        return default(T);
    }
}

My common interface that's being implemented

public interface IEntityOperationCommand
{
    void Execute(EntityOperationModel operationModel);
}

My consuming class that resolves these dependencies -

public class EntityOperationProvider
{
    private readonly IEntityOperationCommand _TWCommandPrepare = null;
    private readonly IEntityOperationCommand _TWCommandCreateDetails = null;
    private readonly IEntityOperationCommand _TWCommandPostCreation = null;
    private readonly IEntityOperationCommand _TWCommandRaiseEvents = null;
    private readonly IEntityOperationCommand _TWCommandNotification = null;
    private readonly TypedImplementationDependencyInjector _implementationDependencyInjector = null;

    private const string assemblyName = "__assembly_name__";
    private const string namespaceName = "__namespace__";


    public EntityOperationProvider(TypedImplementationDependencyInjector implementationDependencyInjector)
    {
        _implementationDependencyInjector = implementationDependencyInjector;

        _TWCommandPrepare = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "PrepareEntity");
        _TWCommandCreateDetails = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "CreateDetails");
        _TWCommandPostCreation = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "PostCreation");
        _TWCommandRaiseEvents = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "RaiseEvents");
        _TWCommandNotification = _implementationDependencyInjector.ResolveImplementation<IEntityOperationCommand>(assemblyName, namespaceName, "SendNotification");
    }
}

When EntityOperationProvider is injected, its constructor attempts to build dependencies and throws StackOVerflowException.

I expect the EntityOperationProvider object to be created and injected.

Note - I am unable to resolve the issue causing StackOverFlowException, there are no circular dependencies (say PrepareEntity class requiring RaiseEvents object and vice versa) or self dependencies (say CreateDetails trying to recursively create its own object). Any help there would be appreciated.

Also if there is any elegant way of injecting typed dependencies (e.g. Dependency attribute of Unity) rather than injecting IserviceProivder, that would also be helpful.

1 Answer 1

1

If you have to be injecting IServiceProvider then there is a problem with the design as that is a code smell.

Asp.Net Core DI allows for the registration of multiple implementations of an interface. As such it also allows for the injection of that collection into dependent class via IEnumerable<IInterface> into the dependent constructor

For example

public EntityOperationProvider(IEnumerable<IEntityOperationCommand> commands) {
    //...
}

which would internally use IServiceProvider.GetServices<T>() extension

From there it is just a matter of extracting the desired implementation

public class EntityOperationProvider {
    private readonly IEntityOperationCommand _TWCommandPrepare = null;
    private readonly IEntityOperationCommand _TWCommandCreateDetails = null;
    private readonly IEntityOperationCommand _TWCommandPostCreation = null;
    private readonly IEntityOperationCommand _TWCommandRaiseEvents = null;
    private readonly IEntityOperationCommand _TWCommandNotification = null;


    public EntityOperationProvider(IEnumerable<IEntityOperationCommand> commands) {            
        _TWCommandPrepare = commands.OfType<PrepareEntity>().FirstOrDefault();
        _TWCommandCreateDetails = commands.OfType<CreateDetails>().FirstOrDefault();
        _TWCommandPostCreation = commands.OfType<PostCreation>().FirstOrDefault();
        _TWCommandRaiseEvents = commands.OfType<RaiseEvents>().FirstOrDefault();
        _TWCommandNotification = commands.OfType<SendNotification>().FirstOrDefault();
    }

    //...
}
Sign up to request clarification or add additional context in comments.

2 Comments

This looks greate, will rework our current solution and update how suitable this is for us. However <IEntityOperationCommand> has many more implementations (this is client extensible), if all of them are being injected, it is essentially wastage of memory.
@HarshChaturvedi then that exposes some issues with you current design choices. You should revisit the design of your architecture.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.