Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple injector and internal constructors

I am working on a small class library and using Simple Injector for my DI. The class library has an access point (some sort of a service I guess) which is public and it has some internal services and repositories.

I saw that Simple Injector does not support constructor injection with internal constructors. For example, my product service looks like:

     internal class ProductService : IProductService
      {
        private IProductRepository _productRepository;

        internal ProductService(IProductRepository repository)
        {
          if (repository == null) throw new ArgumentNullException("repository");

          _productRepository = repository;
        }

      }

And my setup:

container.Register<IProductService, ProductService>();
container.Register<IProductRepository>(() => new ProductRepository());

When I run the code, I get the following exception:

For the container to be able to create ProductService, it should contain exactly one public constructor, but it has 0.

My questions:

1) Is there a specific reason that injecting internal classes doesn't work in terms of architecture/design?

2) How is this behavior (using dependency injection with classes that should not be public) achieved and is it desired?

like image 805
sTodorov Avatar asked Feb 13 '23 20:02

sTodorov


1 Answers

Simple Injector tries to give you a sensible default. By default it is limited to auto-wiring types with a single public constructor, since having multiple constructors is an anti-pattern. Simple Injector by default only injects into public constructors, since for Simple Injector to be able to safely call a constructor of a type, this must be public. For instance, Simple Injector will not be able to call an internal constructor when the application is running in a (partial trust) sandbox, and although invoking internal constructors is possible in full trust, creating such type is slower. For performance it is best to keep the type and the constructor public.

Besides these technical constraints, under normal conditions components and their constructors will be public, because you would normally always have external consumers that need to access that component. Examples of such consumers are your unit test projects and your composition root project.

So the sensible default is 'one public constructor', but the type itself doesn't have to be public, although resolving internal types will be slower and might not always work in a sandbox. In other words, when you're not running in a sandbox (such as Silverlight or Windows Phone), Simple Injector will be able to resolve internal types, as long as they have a single public constructor.

But if you really need or want your constructors to be internal, the constructor resolution behavior can be overridden by implementing and registering a custom IConstructorResolutionBehavior. Here's an example:

public class InternalConstructorResolutionBehavior : IConstructorResolutionBehavior
{
    private IConstructorResolutionBehavior original;

    public InternalConstructorResolutionBehavior(Container container) {
        this.original = container.Options.ConstructorResolutionBehavior;
    }

    public ConstructorInfo GetConstructor(Type implementationType) {
        if (!implementationType.GetConstructors().Any()) {
            var internalCtors = implementationType.GetConstructors(
                BindingFlags.Instance | BindingFlags.NonPublic)
                .Where(c => !c.IsPrivate)
                .ToArray();

            if (internalCtors.Length == 1) return internalCtors.First();
        }

        return this.original.GetConstructor(implementationType);
    }
}

This custom constructor resolution behavior can be registered as follows:

var container = new Container();

container.Options.ConstructorResolutionBehavior = 
    new InternalConstructorResolutionBehavior(container);

// Normal registrations here
like image 164
Steven Avatar answered Feb 28 '23 18:02

Steven