I have the following which is packed into a nuget package
// Internal implementation
internal interface ICoreService
{
int GetSomething();
}
internal class CoreService : ICoreService
{
int ICoreService.GetSomething()
{
return 2;
}
}
// Exposed Service
public interface IExposedService
{
int GetNumber();
}
public class ExposedService: IExposedService
{
private readonly ICoreService _coreService;
internal ExposedService(ICoreService coreService)
{
_coreService = coreService;
}
public int GetNumber()
{
return _coreService.GetSomething();
}
}
There are 2 extension methods for the IServiceCollection
public static IServiceCollection Initialize(this IServiceCollection services)
{
services.AddSingleton<ICoreService, CoreService>();
return services;
}
public static IServiceCollection AddExposedService(this IServiceCollection services)
{
services.AddTransient<IExposedService, ExposedService>();
return services;
}
These are chained for example : builder.Services.Initialize().AddExposedService()
.
Now the issue is that since ICoreService
is internal, when adding this package to another app, the DI cannot resolve ICoreService and inject it into ExposedService.
But if I do the following it works just fine :
public static IServiceCollection Initialize(this IServiceCollection services)
{
services.AddSingleton<ICoreService, CoreService>();
return services;
}
public static IServiceCollection AddExposedService(this IServiceCollection services)
{
services.AddTransient<IExposedService>(sp =>
{
var coreService = sp.GetRequiredService<ICoreService>();
return new ExposedService(coreService)
});
return services;
}
The goal is to hide the CoreService so when adding the package, you can't inject ICoreService into anything you want.
The question : Is this approach of resolving the ICoreService and using poor man's DI common practice? If not, how else could you go about it? Additionally, what is happening under the hood that allows this approach to work?
I encountered the following error when testing your first approach. Since you haven't provided any error details, I'm sharing it here to clarify the issue. Please confirm that this is the same error you've encountered.
System.InvalidOperationException: "A suitable constructor for type 'Package.A.ExposedService' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."
Understanding the Error:
This error arises when the dependency injection framework cannot directly create an instance of the ExposedService
class, usually because its constructor is marked as internal
. Dependency injection frameworks require public constructors to instantiate and manage the dependencies of your classes.
Your provided Solution:
Your solution using a factory delegate is absolutely correct! Here's a breakdown of how it works:
services.AddTransient<IExposedService>(sp =>
{
var coreService = sp.GetRequiredService<ICoreService>();
return new ExposedService(coreService);
});
Bypassing Restrictions: This technique bypasses the internal
constructor restriction. The delegate provides the dependency injection framework with a way to instantiate ExposedService
instances.
Encapsulation Preserved: Importantly, this approach doesn't compromise encapsulation. The internal details of Package A
stay protected, while you expose only the IExposedService
interface.
I hope this helps and let me know if you have any further questions.