Search code examples
c#simple-injector

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

Solution

  • 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.