Dependency Injection of Internals in .NET Core 2.1

Our guest blogger, Andrew Hinkle, shows us a technique on how to use dependency injection with internals in .NET Core 2.1.

Written by Andrew Hinkle • Last Updated: • Develop •
Light coming from chest

When working with Legacy applications you'll find class libraries littered with public classes implemented by other class libraries that were never meant for consumption. To mitigate the object leakage developers change public classes to internal and expose their usage via a few limited public classes. That's great, but how do you inject internal dependencies into the class library when the dependency injection occurs from the entry project such as an API? Let's start with the basics and work our way to the solution.

Taking advantage of "poor man's dependency injection" (PMDI) all of your internal classes will have two constructors. The public constructor expects an implementation of an interface and an internal constructor that creates a new instance of the implementation. This tightly couples the implementation details with the class. The best part is that you can add unit tests that inject mock versions (TestDoubles) of the interfaces such as repositories, configuration settings, etc. This allows you to test just the logic in the class and not what is injected.

Of course you can take it to the next stage and remove the second constructor causing the tight coupling. To accomplish this feat you now move the creation of the implementation in the calling class. At this point you've just moved the cheese up a layer that now maintains that dependency. In order to take full advantage of this we introduce Inversion of Control frameworks.

Here's the rub, once you get out of the class library, you can't use the internal classes you want to use for the implementation. Sure, you could change them to public classes, but now your library is naked for all to see. You could update the class library properties to add an InternalsVisibleTo to the calling assembly, but that defeats the whole purpose given the tight coupling to the assemblies. Perhaps one of the IOC frameworks can handle registering internals, but I haven't found it.

While working with .NET Core 2.1 Dependency Injection I ran into the same problem yet again. After reviewing many articles and stackoverflow posts, I finally came up with a solution I like. The crux of it is to create a public class called ServiceCollectionForBusiness (For {project name}) and interface in the class library that will register the dependencies. Here's the tricky part, you need to inject that class into the Program.cs, so it can be injected into the Startup.cs, and so it can then register the class library internal dependencies.

Sounds a bit like injection inception?

In the interest of code overload, I've created a sample WebApi .NET Core 2.1 that demonstrates the concept titled DependencyInjectionOfInternals. This simple app has a class library that processes internal CommandA, CommandB, and CommandC that implement ICommand. Note that these commands have external dependency of BusinessConfiguration that we'll need to inject later.

Commands/CommandA.cs

internal class CommandA : ICommand
{
    private readonly BusinessConfiguration _businessConfiguration;

    public CommandA(BusinessConfiguration businessConfiguration)     {         _businessConfiguration = businessConfiguration;     }
    public string Process(ProcessRequest request)     {         // Do something         var connectionString = _businessConfiguration.ConnectionString;
        // Return a message to demonstrate when the command was processed.         return $@"CommandA was processed.  ConnectionString: {connectionString}";     } }

Commands/CommandB.cs

internal class CommandB : ICommand
{
    public string Process(ProcessRequest request)
    {
        // Do something
        // Return a message to demonstrate when the command was processed.
        return "CommandB was processed";
    }
}

Commands/CommandC.cs

internal class CommandC : ICommand
{
    private readonly BusinessConfiguration _businessConfiguration;

    public CommandC(BusinessConfiguration businessConfiguration)     {         _businessConfiguration = businessConfiguration;     }
    public string Process(ProcessRequest request)     {         // Do something         var documentPath = _businessConfiguration.DocumentPath;
        // Return a message to demonstrate when the command was processed.         return $@"CommandC was processed.  DocumentPath: {documentPath}";     } }

Commands/ICommand.cs

internal interface ICommand
{
    string Process(ProcessRequest request);
}

The Commands are created via an internal CommandFactory (an abstract factory) that uses the .NET Core ServiceProvider to return the list of all Commands or a specific ICommand given the CommandType.

Commands/ICommandFactory.cs

internal interface ICommandFactory
{
    IEnumerable<ICommand> Create(CommandType commandType);
}

Commands/CommandFactory.cs

internal class CommandFactory : ICommandFactory
{
    private readonly IServiceProvider _serviceProvider;

    public CommandFactory(IServiceProvider serviceProvider)     {         _serviceProvider = serviceProvider;     }
    public IEnumerable<ICommand> Create(CommandType commandType)     {         // Services will returned in the order they were registered in the Startup.         var commands = _serviceProvider.GetServices<ICommand>();         commands = commandType == CommandType.All             ? commands             : commands.Where(x => x.GetType().Name.Equals(commandType.ToString()));         return commands;     } }

These Commands are the core of this feature and nothing outside of the class library needs to know these internal implementations. The functionality is exposed via the BusinessService class.

Here, the internal CommandFactory is injected. Typically, this class would have to be public following the PMDI technique as mentioned earlier. Without the capability of injecting internals the class library would have to be all PMDI violating the principles of a complete DI solution where every class has a single constructor.

BusinessService.cs

internal class BusinessService : IBusinessService
{
    private readonly ICommandFactory _commandFactory;

    public BusinessService(ICommandFactory commandFactory)     {         _commandFactory = commandFactory;     }
    public ProcessResponse Process(ProcessRequest request)     {         var response = new ProcessResponse();         foreach (var command in _commandFactory.Create(request.CommandType))         {             response.Messages.Add(command.Process(request));         }
        return response;     } }

In order for the API project to use this class library the IBusinessService interface is made public. The only way to use the BusinessService class is by using DI.

IBusinessService.cs

public interface IBusinessService
{
    ProcessResponse Process(ProcessRequest request);
}

I favor leaving POCOs as POCOs without interfaces, so the following public POCOs are exposed for use with IBusinessService. If there were POCOs that were only used within the class library, they would be internal.

Models/CommandType.cs

public enum CommandType
{
    None = 0,
    All = 1,
    CommandA = 2,
    CommandB = 3,
    CommandC = 4,
}

Models/ProcessRequest.cs

public class ProcessRequest
{
    public CommandType CommandType { get; set; }
}

Models/ProcessResponse.cs

public class ProcessResponse
{
    public ProcessResponse()
    {
        Messages = new List<string>();
    }
    // Leave the setter public for deserialization.
    public List<string> Messages { get; set; }
}

As mentioned earlier, the class library has an external dependency on the appsettings.json represented under the BusinessConfiguration section by a ConnectionString and DocumentPath.

Configuration/BusinessConfiguration.cs

public class BusinessConfiguration
{
    public string ConnectionString { get; set; }
    public string DocumentPath { get; set; }
}

The goal with these next classes is to separate the concerns of dependency injection. We want the ability to add a class library to the solution and register a single class from that library that knows how to register all of its dependencies. This leaves the Startup clean with a few registrations making it easier to read and maintain.

Configuration/ServiceCollectionForBusiness.cs

public class ServiceCollectionForBusiness : IServiceCollectionForBusiness
{
    public void RegisterDependencies(IConfiguration configuration, IServiceCollection services)
    {
        // Bind the configuration to 
        var config = new BusinessConfiguration();
        configuration.Bind(nameof(BusinessConfiguration), config);
        services.AddSingleton(config);

        // Setup relationship between public interfaces and internal classes         services.AddScoped<IBusinessService, BusinessService>();
        // Setup relationship between internal interfaces and internal classes         services.AddScoped<ICommandFactory, CommandFactory>();
        // Services will returned in the order they were registered in the Startup.         services.AddScoped<ICommand, CommandB>();         services.AddScoped<ICommand, CommandA>();         services.AddScoped<ICommand, CommandC>();     } }

Configuration/IServiceCollectionForBusiness.cs

public interface IServiceCollectionForBusiness
{
    void RegisterDependencies(IConfiguration configuration, IServiceCollection services);
}

Now we switch over to the API project and update the Program.cs to configure these services before the Startup.cs.

Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>         WebHost.CreateDefaultBuilder(args)             // Add custom service collection registration.
            // In order to inject classes into the             // Startup constuctor, the services must             // be registered before we UseStartup.
            // ConfigureServices must be called before             // UseStartup method.  This is how .NET Core             // works under the covers as noted in:             // Asp.Net Core: Injecting custom              // data/classes into startup classes’             // constructor and configure method             // by Matt Mazzola.             // https://medium.com/@mattmazzola/asp-net-core-injecting-custom-data-classes-into-startup-classs-constructor-and-configure-method-7cc146f00afb
            .ConfigureServices(services => services.AddTransient<IServiceCollectionForBusiness, ServiceCollectionForBusiness>())             .UseStartup<Startup>(); }

This gives us the awesome ability to inject the IServiceCollectionForBusiness into the Startup.

Startup.cs

public class Startup
{
    // Inspiration
    // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1
    // https://stackoverflow.com/questions/49703773/implement-ado-connection-in-asp-net-core
    // https://medium.com/@mattmazzola/asp-net-core-injecting-custom-data-classes-into-startup-classs-constructor-and-configure-method-7cc146f00afb

    private readonly IServiceCollectionForBusiness _serviceCollectionForBusiness;
    public Startup(IConfiguration configuration,                    IServiceCollectionForBusiness serviceCollectionForBusiness)     {         _serviceCollectionForBusiness = serviceCollectionForBusiness;         Configuration = configuration;     }

    public IConfiguration Configuration { get; }
    // This method gets called by the runtime.     // Use this method to add services to the container.     public void ConfigureServices(IServiceCollection services)     {         AddMvc(services);         AddBusinessLibrary(services);     }
    private static void AddMvc(IServiceCollection services)     {         var namespaceToTypes = typeof(ProcessRequest).Namespace;
        services.AddMvc()             .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            // I prefer that Enums should be serialized             // as their string names, not their integer             // value.  As always, it depends on your app.             // Adding the setting here saves us from             // having references to Newtonsoft.Json             // in the project libraries that don't care             // about this "presentation" concern.
            .AddJsonOptions(options =>             {                 // Indented to make it easier to read during this demo.                 options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();                 options.SerializerSettings.Converters.Add(new StringEnumConverter());                 options.SerializerSettings.Formatting = Formatting.Indented;                 //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;                 options.SerializerSettings.SerializationBinder = new CustomJsonSerializationBinder(namespaceToTypes);                 options.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;             });     }
    // The IBusinessService is defined in the     // same project as BusinessService, coupling the     // Tips.DependencyInjectionOfInternals project to     // the Tips.DependencyInjectionOfInternals.Business     // project.

    // We could isolate the IBusinessService to it's     // own project to support full abstraction allowing     // for another project to be added that implements     // IBusinessService and change the dependency     // injection settings here to inject that instead.     // However, since we would still have to couple     // these two projects to add this registration,     // we wouldn't have gained anything.
    // While Dependency Injection is great, don't     // overcomplicate the dependencies when you know     // there will only be one implementation ever.     // With the pattern followed in this solution,     // it's easy for us to refactor the interface     // out later if we have a need.
    private void AddBusinessLibrary(IServiceCollection services)     {         _serviceCollectionForBusiness.RegisterDependencies(Configuration, services);     }
    // This method gets called by the runtime.     // Use this method to configure the HTTP request pipeline.     public void Configure(IApplicationBuilder app, IHostingEnvironment env)     {         if (env.IsDevelopment())         {             app.UseDeveloperExceptionPage();         }         else         {             app.UseHsts();         }

        app.UseHttpsRedirection();         app.UseMvc();     } }

Here's the controller that kicks off all of the DI gloriousness.

Controllers/CommandController.cs

[ApiController]
public class CommandController : ControllerBase
{
    private readonly IBusinessService _businessService;

    public CommandController(IBusinessService businessService)     {         _businessService = businessService;     }
    [Route("v1/api/commands/{commandType=1}")]     [HttpGet]     public ActionResult Get(int commandType)     {         var request = new ProcessRequest { CommandType = (CommandType)commandType };         var response = _businessService.Process(request);         return Ok(response);     } }

The appsettings.json is pretty straightforward. It uses dummy data here. Legit secrets should be hidden away from source control by Managing User Secrets in .NET Core 2.0 Apps.

app.settings

{
  "BusinessConfiguration": {
    "ConnectionString": "Super Secret Database Connection String that should be hidden by managing User Secrets.",
    "DocumentPath":  "A path to server storage." 
  }
}

Sprinkle in some unit tests for the internals class library.

ServiceCollectionForBusinessTest.cs

[TestClass]
public class ServiceCollectionForBusinessTest
{
    private readonly ServiceProvider _serviceProvider;

    // Only use [ClassInitialize] when the class properties     // should only be initalized once for all tests.
    // If the test methods change the properties,     // use [TestInitialize] or add a public constructor.
    // If you're not sure, just use the unit test's     // public constructor.     public ServiceCollectionForBusinessTest()     {         // The mock configuration must setup all of the         // expected properties or an exception is thrown.
        // System.ArgumentNullException: Value cannot be null.         // Value cannot be null.\r\nParameter name: configuration
        var mockConfigurationSection = new Mock<IConfigurationSection>();         mockConfigurationSection.SetupGet(x => x[It.IsAny<string>()]).Returns("expected configuration value");
        var mockConfiguration = new Mock<IConfiguration>();         mockConfiguration.Setup(x => x.GetSection(It.IsAny<string>())).Returns(mockConfigurationSection.Object);
        var serviceCollection = new ServiceCollection();
        var serviceCollectionForBusiness = new ServiceCollectionForBusiness();
        serviceCollectionForBusiness.RegisterDependencies(mockConfiguration.Object, serviceCollection);         _serviceProvider = serviceCollection.BuildServiceProvider();     }
    [TestMethod]     public void VerifyRegisterDependenciesForBusiness()     {         Assert.IsInstanceOfType(_serviceProvider.GetService<IBusinessService>(), typeof(BusinessService));         Assert.IsInstanceOfType(_serviceProvider.GetService<ICommandFactory>(), typeof(CommandFactory));         AssertCommandsWereRegistered();     }
    private void AssertCommandsWereRegistered()     {         var expectedCommands = new List<Type> { typeof(CommandB), typeof(CommandA), typeof(CommandC) };
        var actualCommands = _serviceProvider.GetServices<ICommand>().ToList();
        Assert.IsNotNull(actualCommands);         Assert.AreEqual(expectedCommands.Count, actualCommands.Count);
        for (var i = 0; i < actualCommands.Count; i++)         {             Assert.IsInstanceOfType(actualCommands[i], expectedCommands[i]);         }     } }

And a dash of unit tests for the API.

StartupTest.cs

[TestClass]
public class StartupTest
{
    // Requires AspNetCore dependency for the
    // ServiceCollection to call the static
    // extension method AddMvc().
    [TestMethod]
    public void VerifyRegisterDependenciesForBusinessWasRegistered()
    {
        // Requires AspNetCore dependency for the
        // ServiceCollection to call the static
        // extension method AddMvc().
        var serviceCollection = new ServiceCollection();

        var mockConfiguration = new Mock<IConfiguration>();
        var mockServiceCollectionForBusiness = new Mock<IServiceCollectionForBusiness>();         mockServiceCollectionForBusiness.Setup(x => x.RegisterDependencies(mockConfiguration.Object, serviceCollection));
        var startup = new Startup(mockConfiguration.Object, mockServiceCollectionForBusiness.Object);
        startup.ConfigureServices(serviceCollection);
        // Verify that the static method was called once.         mockServiceCollectionForBusiness.Verify(x => x.RegisterDependencies(mockConfiguration.Object, serviceCollection), Times.Once);     } }

Of course you still need to run an integration test to make sure everything was hooked up correctly. Here's the result for each CommandType.

None: /v1/api/commands/0

{
    "messages": []
}

All: /v1/api/commands/1

{
    "messages": [
     "CommandB was processed",
     "CommandA was processed.  ConnectionString: Super Secret Database Connection String that should be hidden by managing User Secrets.",
     "CommandC was processed.  DocumentPath: A path to server storage."
    ]
}

CommandA: /v1/api/commands/2

{
    "messages": [
     "CommandB was processed",
     "CommandA was processed.  ConnectionString: Super Secret Database Connection String that should be hidden by managing User Secrets.",
     "CommandC was processed.  DocumentPath: A path to server storage."
    ]
}

CommandB: /v1/api/commands/3

{
    "messages": [
     "CommandB was processed"
    ]
}

CommandC: /v1/api/commands/4

{
    "messages": [
     "CommandC was processed.  DocumentPath: A path to server storage."
    ]
}

The entire application is available on GitHub under DependencyInjectionOfInternals.

Conclusion

I've shown how we can separate the concerns of dependency injection by implementing that logic in the class library. While this makes it possible to use .NET Core to dependency inject your internal classes, if you prefer to keep everything public, this technique still works.

Do you create internal classes or do you keep everything public? Do you prefer to only implement internal classes when the project can be consumed externally via NuGet package or by a third party? Do you like the concept of separating the concerns of dependency inject down into the class libraries? Let's discuss!

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Andrew Hinkle

Andrew Hinkle has been developing applications since 2000 from LAMP to Full-Stack ASP.NET C# environments from manufacturing, e-commerce, to insurance.

He has a knack for breaking applications, fixing them, and then documenting it. He fancies himself as a mentor in training. His interests include coding, gaming, and writing. Mostly in that order.

comments powered by Disqus