I'm using SimpleInjector v3.2.7. The failure happens when trying to construct a Web Api HeartbeatController
. Its constructor looks like this:
public HeartbeatController(IHeartbeatService heartbeatService, IMainDbVitalCheck mainDbVitalCheck, ICacheVitalCheck cacheVitalCheck)
When calling container.GetCurrentRegistrations()
(before container.Verify()
), I see a registration for IHeartbeatService
that is mapped to HeartbeatService
- this is the mapping I want.
But the call to Verify()
fails with the following error:
The configuration is invalid. Creating the instance for type IHttpController failed. The constructor of type HeartbeatController contains the parameter with name 'heartbeatService' and type IHeartbeatService that is not registered. Please ensure IHeartbeatService is registered, or change the constructor of HeartbeatController.
Here is my container setup:
Global.asax.cs
GlobalConfiguration.Configure(WebApiConfig.Register);
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
SimpleInjectorWebCommon.SetUpWebApi(config);
// ...
// Other unrelated stuff
// ...
}
SimpleInjectorWebCommon.cs
public static Container DiContainer { get; private set; }
public static void SetUpWebApi(HttpConfiguration config)
{
InitContainer(config);
config.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(DiContainer);
}
public static void InitContainer(HttpConfiguration config)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.Options.ConstructorResolutionBehavior = new GreediestConstructorBehavior();
container.Options.AllowOverridingRegistrations = true;
RegisterServices(container);
container.RegisterWebApiControllers(config);
container.Verify();
DiContainer = container;
}
private static void RegisterServices(Container c)
{
// ... other registrations
RegisterSingleImplementations(c);
// ... other registrations
}
// My gut tells me the problem is somewhere in here
private static void RegisterSingleImplementations(Container c)
{
var assemblies = GetAllBinAssemblies("DSI.*.dll").ToList();
foreach (var assembly in assemblies)
{
var registrations = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract)
.Where(t => t.Namespace != null && t.Namespace.StartsWith("DSI."))
.Where(t => GetDirectInterface(t.GetInterfaces()) != null)
.Select(t => new { Service = GetDirectInterface(t.GetInterfaces()), Implementation = t});
foreach (var reg in registrations)
{
try
{
c.Register(reg.Service, reg.Implementation, Lifestyle.Scoped);
}
catch(Exception e)
{
}
}
}
Update
The problem was in how I was loading assemblies during scanning to get a list of types to register. I used Assembly.Load
which would eventually cause the assembly to appear twice in the current AppDomain
. I since switched to using AppDomain.CurrentDomain.Load
and now the assembly only appears once in the app domain. Here is the implementation of assembly scanning:
private static IEnumerable <Assembly> GetAllBinAssemblies(string searchPattern)
{
var assemblyPath = AppDomain.CurrentDomain.BaseDirectory;
var privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
if (string.IsNullOrEmpty(privateBinPath))
{
return GetAssemblies(assemblyPath, searchPattern);
}
if (Path.IsPathRooted(privateBinPath))
{
return GetAssemblies(privateBinPath, searchPattern);
}
return
privateBinPath.Split(';').SelectMany(bin => GetAssemblies(Path.Combine(assemblyPath, bin), searchPattern));
}
private static List <Assembly> GetAssemblies(string path, string searchPattern)
{
return Directory
.GetFiles(path, searchPattern)
.Select(f => AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(f)))
.ToList();
}
It's hard to say, but Simple Injector is typically not wrong ;-) so you can rest assure that the IHeartbeatService
interface that HeartbeatController
depends upon is not registered.
There are typically two reasons what can go wrong here:
To check whether the registered IHeartbeatService
is the same type as the HeartbeatController
's constructor argument, run the following code just before calling container.Verify()
and after RegisterWebApiControllers
:
var heartbeatServiceRegistration = (
from r in container.GetCurrentRegistrations()
where r.ServiceType.Name == "IHeartbeatService"
select r)
.Single();
var parameterType =
typeof(HeartbeatController).GetConstructors().Single()
.GetParameters().First().ParameterType;
bool areSame =
object.ReferenceEquals(parameterType, heartbeatServiceRegistration.ServiceType);
if (!areSame) throw new Exception("different types");
container.Verify();
If areSame
is false, it means that there in fact are multiple interfaces named IHeartbeatService
for whatever reason.