Daniel Gabriel Daniel Gabriel - 28 days ago 6
C# Question

A type is listed in GetCurrentRegistrations, but Verify fails saying type not registered

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();
}

Answer

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:

  1. You accidentally defined a second interface in the code base with the same name, while you registered the other.
  2. The same assembly is loaded twice in the AppDomain under a different identity (for instance when the same assembly gets loaded from two locations), which causes there to be 2 interfaces at runtime.

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.