12

Background

I am working on a class library in support of a web site. The library combines related APIs from several different vendors, all of which have their own particular nuances and domain objects. As an example, imagine there is a credit card provider that exposes a BankAccount object, a loans provider that exposes an Account, and a checking account system exposes an AccountDto. The primary design goal of the library is to hide these various implementations and allow the web site to work with a single concept of Account, with a set of services that decide which vendor to call and how to coordinate the data from the various sources.

To hide the confusion and keep the API clean, only my own domain objects and services are public. Inside the library, each vendor has its own repository and domain objects. The vendor's classes are all marked internal to keep the web developers from circumventing the services layer (either intentionally or unintentionally).

So the flow goes like this:

Web Site >> My API >> Service Layer (public) >> Repository Layer (internal)

This solution uses dependency injection and the Unity IoC container. My APIs' services are registered in the composition root and injected directly into the web sites' controllers as constructor arguments. All of this works fine.

The problem

Here is the problem I am struggling with. My services have constructor arguments that allow the repositories to be injected into them. But the repositories are internal so the web site cannot register them in the composition root (without doing something truly bizarre with Reflection). Plus I don't want the web site developers to worry about injecting them.

So the question is: How do I inject the internal/private repositories into the public service layer, while adhering to the accepted conventions in a DI approach?

My proposed solution

The way I am doing this right now is as follows:

I have added an abstract RepositoryLocator interface and class to the library. It has its own Unity container. It is left unsealed so that a unit testing project can derive its own version and substitute stub repositories.

public interface IRepositoryLocator
{
    T GetRepository<T>() where T : IRepository;
}

public abstract class BaseRepositoryLocator : IRepositoryLocator
{
    protected readonly UnityContainer _container = new UnityContainer();

    public virtual T GetRepository<T>() where T : IRepository
    {
        return _container.Resolve<T>();
    }
}

Then I added a DefaultRepositoryLocator to my library that is hardcoded to provide the production runtime repositories.

public sealed class DefaultRepositoryLocator : BaseRepositoryLocator
{
    public DefaultRepositoryLocator()
    {
        RegisterDefaultTypes();
    }
    private void RegisterDefaultTypes()
    {
        _container.RegisterType<IVendorARepository, VendorARepository>();
        _container.RegisterType<IVendorBRepository, VendorBRepository>();
        _container.RegisterType<IVendorCRepository, VendorCRepository>();
    }
}

Then in my services classes, instead of injecting repositories, I inject the repository locator, and pull out the repositories that are needed in the constructor.

public class AccountService : IAccountService
{
    internal readonly IVendorARepository _vendorA;
    internal readonly IVendorBRepository _vendorB;

    public AccountService(IRepositoryLocator repositoryLocator)
    {
        _vendorA = repositoryLocator.GetRepository<IVendorARepository>();
        _vendorB = repositoryLocator.GetRepository<IVendorBRepository>();
    }
}

The web development team's only task

In the web site's composition root, they just need to register the default repository locator in their Unity container.

container.RegisterType<IRepositoryLocator, DefaultRepositoryLocator>();

Once they do that, the Unity container injects the repository locator into all the services that are injected into controllers, and the services can retrieve the repositories they need.

Unit testing

In a unit testing project, the unit testing assembly will be given access to the internal members using the InternalsVisibleTo attribute, and the test project would substitute its own StubRepositoryLocator conformed to the internal-but-now-public interfaces.

Things that are troublesome about this design

So, all you IoC experts, what sort of problems have I created? Here's what I worry about

  1. There are two Unity containers, one in the web application and one in the library's RepositoryLocator. I hear there really ought to be only one, but I also read that it is an anti-pattern to pass it around.
  2. The RepositoryLocator is hardcoded for production runtime. I don't think harcoding is a cardinal sin like many engineers do, but I do regard it with suspicion.
  3. The unit testing project will need InternalsVisibleToAttribute added to the library. This is definitely a code smell and I wonder how we would deal with it if the unit testing project changes its name or if there needs to be several of them.

  4. The constructors for my services take one injection for the RepositoryLocator itself, instead of separate injections for the individual repositories. This conceals the individual dependencies at compile time.

Is there an approach that resolves these issues and is consistent with IoC best practices, while still concealing the repository layer behind internal scope?

7
  • 7
    I don't agree with #3: InternalsVisibleTo is an important tool for unit tests, only using it outside the scope of tests is a code smell. May 23, 2017 at 7:09
  • 1
    #1 would also happen if your lib would be provided by a third party vendor as a black box and you could not change it (you then would not even know if there is a second container). So don't invest too much thought about it.
    – Doc Brown
    May 23, 2017 at 19:51
  • Keep it stupid simple. Instead of jumping through hoops to create a repository API that doesn't have a public interface, give it a public interface.
    – RubberDuck
    Jul 23, 2017 at 12:12
  • Yep. Hiding the repository interface makes things less simple for me but simpler for the caller, which is the whole point of the library.
    – John Wu
    Jul 23, 2017 at 17:25
  • My quest for doing the DI in Class Libraries in right way brought me here. I have designed my app similar to yours. but I have written a BootStrap Method in DLL which takes the instance of the container. In the Web App, At the time of container initialization i am calling the dll bootstrap passing the container instance. Have you faced any issues so far? Jul 31, 2017 at 5:40

4 Answers 4

3

In the web site's composition root, they just need to register the default repository locator

IMO the web site doesn't even need to know about your repositories. As you said, the repos and vendors classes are internal, so your site should not worry about accessing them or injecting them into somewhere. Again, as you said, it is your library's role to abstract multiple vendors and expose a simpler API to the user (which is the web site in your example).

Having said that, I think you should:

  • In your API, provide some initial entry point method, that needs to be executed once by the client in order that your library works appropriately;
  • Within this method (that's within your library), you configure the container with vendors repositories classes (internal to your library);
  • Keep vendors stuff private and your services public
  • That's it: your client (the website) need to worry only about injecting your services, nothing more than that.
  • Optional: you could use configuration file to bind interfaces and implementations

About your question / worries:

  1. You (usually) have no control about what your client will do; the client app might not even inject your classes, it could just use them right away; so don't worry about that, like said in the comments;
  2. Config file might be a better approach for this, easily changeble for production or test environments;
  3. Regarding unit testing purposes, I see no problem with this attribute;
  4. My suggestion solves this
2

You can use a container extension. This allows the assembly containing internal classes to configure its own dependencies rather than requiring the consumer to know about them and configure them.

The documentation shows it like this:

IUnityContainer container = new UnityContainer();
container.AddNewExtension<MyCustomExtension>();

The consumer just adds the extension, and that extension class handles the details.

2

My services have constructor arguments that allow the repositories to be injected into them. But the repositories are internal so the web site cannot register them in the composition root (without doing something truly bizarre with Reflection). Plus I don't want the web site developers to worry about injecting them.

We had the same problem in some of our projects. What worked well for us, was a single public method in the library, that adds all dependencies to the container.

In the library:

public static void AddMyLibraryTypes(this IUnityContainer container)
{
    container.RegisterType<IVendorARepository, VendorARepository>();
    container.RegisterType<IVendorBRepository, VendorBRepository>();
    container.RegisterType<IVendorCRepository, VendorCRepository>();
}

In the web site's composition root:

container.AddMyLibraryTypes();
4
  • Note that this ties your library to a specific IoC container. That's generally not an issue for a given codebase, but it is an issue when you're creating a library that is to be used in multiple codebases. It may be better to split this DI-registering method off into a subproject of its own, specific to a certain container. allowing you to create optional dependencies on specific DI frameworks (e.g. MyLibrary, MyLibrary.Unity, MyLibrary.Autofac, ...) If someone wants to use a different DI container, they can still wire it themselves, or you can create a new lib for the new DI container.
    – Flater
    Oct 4, 2021 at 10:53
  • @Flater Unfortunately, the subproject cannot access the internal stuff of the original project, which is kind of the point of providing such a method. Is there a way around this, except for using something like InternalsVisibleTo?
    – pschill
    Oct 4, 2021 at 11:27
  • Generally speaking, this is why I see a lot of developers advocate for not using internal. Really, all it does is clean up some of the Intellisense. In cases where there is an actual security concern, you can argue internal has a bigger purpose, but then it'd often be more interesting to host your library as a service under your control instead of giving people access to the library (or its compiled output, which can be reverse engineered anyway). If the result of tinkering with things you shouldn't is that you break your own thing, then there's no need to actively shield it.
    – Flater
    Oct 4, 2021 at 12:03
  • So in short, I'd frame challenge the notion that you need to use internal here. And if you insist on using it, then having to work around the inaccessibility is something you have to obviously accept and deal with. If that means resorting to InternalsVisibleTo or not trying to access internals externally, then so be it. That's the consequence from choosing to use internal, as this is how it works by design. Implementing a strong access control and then immediately needing to start circumventing is is maybe not impossible but it inherently creates tension in your code structure.
    – Flater
    Oct 4, 2021 at 12:05
0

The simple solution is to declare the interface public and its members as internal.
This way the interface is visible (unprotected) and can be referenced by a public constructor, but the accessibility of its members is internal (protected).
There is no need to implement your Service Locator anti-pattern.

The protection level of the concrete types i.e. implemntations of the interface can be internal to hide them.

The "problem" with dependency containers is that they usually require the registered type to define the constructor's accessibility as public. And therefore, the injected interfaces must be public too.
But since we can define interface members as internal, we can make the interface public and still hide its members from the public. This way we can register a public type that is part of our API, and allow it to depend on types and their members that are only internally visible.

Note: interface members that are hidden i.e. defined as internal, must be implemented explicitly (Explicit Interface Implementation (C# Programming Guide)).

To make internal types of an assembly visible to other assemblies, we must have a file that applies the IsVisibleToAttribute.

Note: it is not required to add this attribute to each file that contains internal types.
Only a single file in this assembly needs to define the friend assembly or assemblies by applying the IsVisibleToAttribute.
That is because the attribute is applied at the assembly level. This means that it can be included at the beginning of any source code file, or it can be included in the AssemblyInfo.cs file in a Visual Studio project.

I recommend to add a AssemblyInfo.cs file (any name of preference is allowed) to the project that needs to make its internals visible e.g. to the assembly that composes the application's dependency graph.
If the project is targeting .NET Framework, then it usually already contains the AssemblyInfo.cs file.
If the project targets .NET Core or later, you must add this file manually.

Assuming that the IoC conatainer is configured in an assembly named Bootstrapper and we have an additional assembly named UnitTest, the AssemblyInfo.cs file could look as followed:

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Bootstrapper")] 
[assembly: InternalsVisibleTo("UnitTest")] 

namespace MyNamespace
{
  public class AssemblyInfo
  {
  }
}

Example

Given is an assembly named Data and an assembly named Bootstrapper that configures the IoC container.

There is a public class Repository (API) that updates the underlying database. It exposes a public method WriteAsync that accepts a IDataModel as argument.
The Repository then converts this type to the internal type DataEntity, which is the hidden entity model for the database.
The DataEntity instance is created using an internal method IDataEntityFactory.Create, which is injected into the public constructor of Repository. Therefore the IDataEntityFactory interface itself is public (but not its members).

Repository and IDataModel are meant to be used as API outside their assembly (public protection level), while the IDataEntityFactory and DataEntity are meant to be hidden from the public API (internal protection level):

DataEntity.cs
internal type in Data assembly.

// Internal database entity type
internal class DataEntity
{
  int Id { get, set; }
}

IDataEntityFactory.cs
public interface type in Data assembly. But the members are internal.

// Make interface public to allow it to be used with a public constructor
public interface IDataEntityFactory
{
  // Only visible in the scope of the defining assembly
  internal DataEntity Create(IDataModel model);
}

DataEntityFactory.cs
internal type in Data assembly.

// Make implementation internal to hide it.
// Since we have defined the IsVisibleToAttribute in the AssemblyInfo.cs file (see above),
// this type is visible to the Bootstrapper assembly for configuration with the application's IoC container.
internal class DataEntityFactory : IDataEntityFactory
{
  // Only visible in the scope of the defining assembly.
  // Since the interface members are hidden/internal, 
  // we must implement them explicitly.
  DataEntity IDataEntityFactory.Create(IDataModel model);
}

Repository.cs
public type in Data assembly with a dependency to the IDataEntityFactory.
Since IDataEntityFactory is public, it can be injected into the public class Repository, but the members of the interface are protected and only visible in the defining assembly Data of IDataEntityFactory.

public Repository
{
  private IDataEntityFactory EntityFactory { get; }

  // Public constructor can use the 'public' interface that has the 'internal' members
  public Repository(IDataEntityFactory entityFactory)
  {
    this.EntityFactory = entityFactory;
  }
  
  public async Task WriteAsync(IDataModel model)
  {
    // Invoke internal method 'IDataEntityFactory.Create()' and use internal type 'DataEntity'.
    // Since we are in the internal assembly scope of the 'Data' assembly the internal 'IDataEntityFactory.Create()' is visible.
    DataEntity entity = this.EntityFactory.Create(model);
    
    // Example access of the internal database resources e.g. using an ORM framework like EF Core
    await using var dbContext = new DbContext();
    dbContext.Add(entity);
    await dbContext.SaveChangesAsync();
  }
}

ApplicationComposer.cs
In the Bootstrapper assembly we have the ApplicationComposer class that configures the IoC container. Since we have declared the Bootstrapper assembly explicitly as friend in the AssemblyInfo.cs located in the Data assembly, ApplicationComposer can see all types in the Data assembly, .

using Data;

class ApplicationComposer
{
  public void ConfigureApplication()
  {
    var serviceCollection = new ServiceCollection();

    // Since this type is in a friend assembly of the 'Data' assembly,
    // we can access the 'internal' class 'DataEntityFactory' to register it as export.
    serviceColection.AddSingleton<IDataEntityFactory, DataEntityFactory>();
  }
}

ClientService.cs
Given is a third assembly Client, that contains a class ClientService to "test" the protection level of the internal types and members of the Data assembly. We have the following visibilities:

using Data;

class ClientService
{
  public async Task WriteDataAsync(IDataModel model)
  {
    // 'Repository' and its members are 'public'. We can access it.
    var repository = new Repository();
    await repository.WriteAsync(model);

    // The following line does not compile!
    // The interface 'IDataEntityFactory' is visible (protection level 'public'), 
    // but the implementation 'DataEntityFactory' is hidden (protection level 'internal')
    IDataEntityFactory entityfactory = new DataEntityFactory();  

    // The following line does not compile!
    // The return type 'DataEntity' is hidden (protection level 'internal').
    // The local variable 'entityfactory' of type 'IDataEntityFactory' (protection level 'public') is usable, 
    // but the member 'Create()' is hidden (protection level 'internal').
    DataEntity entity = entityfactory.Create(model);
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.