Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac not resolving types from assembly scan in asp.net 5

We have a dnx46 web project that is using autofac assembly scanning during startup to register types. Our project.json dependencies include:

"Autofac.Configuration": "4.0.0-rc1-268",
"Autofac.Extensions.DependencyInjection": "4.0.0-rc1-177",
"Autofac.Extras.CommonServiceLocator": "3.2.0",
"Microsoft.AspNet.Hosting": "1.0.0-rc1-final",
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final",
"Microsoft.AspNet.Mvc.ViewFeatures": "6.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.AspNet.Session": "1.0.0-rc1-final",
"Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
"Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc1-final",
"Newtonsoft.Json": "8.0.3"

Our startup.cs ConfigureServices method looks like this:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(serviceType => Configuration);
    services.AddInstance<Microsoft.Extensions.Configuration.IConfiguration>(Configuration);

    services.AddCaching();
    services.AddSession();
    services.AddMvc();

    var builder = new ContainerBuilder();

    var assemblies = Directory.GetFiles(<ourBinDirectoryPath>, "*.dll", SearchOption.TopDirectoryOnly).Select(Assembly.LoadFrom);

    foreach (var assembly in assemblies)
    {
        builder.RegisterAssemblyTypes(assembly).Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Repository") || t.Name.EndsWith("DataContext")).AsSelf().AsImplementedInterfaces();
        builder.RegisterAssemblyTypes(assembly).Where(t => !t.Name.EndsWith("Service") && !t.Name.EndsWith("Repository") && !t.Name.EndsWith("DataContext")).AsSelf().AsImplementedInterfaces().InstancePerDependency();
    }

    builder.Populate(services);
    var container = builder.Build();

    return container.Resolve<IServiceProvider>();
}

Our process is that we have several projects that we build that drop dlls that this project will need into a custom bin folder. On startup, we scan those assemblies to register the types with Autofac. We did this in a proof of concept project, and it worked without issues. In our new project though, we have the following issues:

  1. If you try to resolve a service in a controller constructor, it throws an error indicating Microsoft Dependency injection cannot resolve the service. Not sure why it is not using the Autofac provider here.
  2. If we manually resolve a single type in the startup along with our assembly scan, we will get an error in the controller construction indicating Autofac could not resolve the service (uses Autofac provider here, but still does not find our types).
  3. If I inspect the builder and the container before it returns in the ConfigureServices, it appears to have all of our types registered. However, if I change the controller to inject the IServiceProvider, and then try to resolve a service from it, it accurately uses Autofac provider, but I always get null back (no service found).

After trying many things, I took our proof of concept project and copied it over to our new solution (in case we missed some small setting or something when setting up our new project), and the bad behavior continued. I am starting to think that our assemblies are introducing some bug (since those are the only things that have changed after the copy), but Autofac does not throw an error during scanning, and instead just doesn't use the types it scanned in correctly.

Edit 1

I have created and posted a very simplified example that reproduces the issue I am running into. You should be able to pull that down and run it to reproduce the error. You should get an error in the home controller constructor indicating service resolution failed.

Note: when running this example, it executes a .bat file that puts output dll from the domain project in your user profile .nuget folder.

Edit 2

Split interface into abstraction folder based on recommendation from @Travis Illig. Once I did, solution started working. We have it split in our "real" project, but it is not working. Still experimenting to figure out why.

Edit 3

So finally got our project working again. Issue ended up being several things:

  1. We had an abstraction project dependency listed in our web project, and we were also loading and scanning the dll from that assembly (double loading). (I think this was the big issue.)
  2. Our NuGet versions were bad, so I don't think they were loading up correctly. We were using 1.0.0-1, which is an invalid prerelease format. Updated it to 1.0.0-b1, and things seemed to start working better.

This took me way longer to figure out, and my biggest issue at this point is that Autofac would fail silently. I really wish it would blow up and give me some debug output or something indicating there was an issue. Instead, everything appeared to scan and load fine, but then when you went to use the container, nothing would resolve.

like image 289
Jeremy Armstrong Avatar asked Mar 25 '16 19:03

Jeremy Armstrong


1 Answers

Chances are the assemblies are being loaded in such an order where some types can't be loaded due to prerequisite assemblies not being loaded.

For example, say you have assembly A that contains interface IComponent. Also say you have assembly B that contains a class Component that implements IComponent. If you load assembly B first, the type Component won't be loadable because the interface it implements isn't defined.

When doing type scanning, Autofac tries to be as safe as possible and won't explode if out runs into types that can't be loaded. You can see in the scanning registration source where it uses a special safe method to do loading.

In your spike code, I'm betting there were far fewer assemblies to load so you ended up loading things in the right order. Happy accident. When you get into larger projects, it's much easier to fall to control the load order and cause issues like this.

like image 121
Travis Illig Avatar answered Oct 23 '22 17:10

Travis Illig