We have been using Autofac as the IoC Container for dependency injection. In order to pass parameters to Web API controllers during the registration, which we were hoping to be passed to the Controller constructor. It is a double typed parameter with the name ratio
in the constructor below.
However, Autofac throws an exception because it cannot resolve the last parameter with the type of double (ratio
here) and cannot create an instance of DataProcessController
.
public DataProcessController(ILogger logger, IDataProvider dataProvider, double ratio)
{
...
}
by using WithParameter
method in order to specify the value to use.
builder.RegisterType<DataProcessController>().AsSelf()
.WithParameter("ratio", 0.4);
Also I call AddControllersAsServices
in order to register the controller as a service and resolve it from the container.
services.AddMvc()
.AddControllersAsServices()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
and call Populate()
after finishing registrations.
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
All defined in the function below:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddHttpClient();
services.AddMvc()
.AddControllersAsServices()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//Autofac
var builder = new ContainerBuilder();
builder.RegisterInstance(_logger).As<ILogger>().SingleInstance();
//All other dependencies are defined here....
builder.RegisterType<DataProcessController>().AsSelf()
.WithParameter("ratio", 0.4);
// auto-discover examples from this assembly
services.AddSwaggerExamplesFromAssemblyOf<Startup>();
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
}
I tried to pass both primitive data types and reference data types like another class as a container to hold the double values, but all failed to resolve the controller by Autofac.
You need to call builder.Populate(services)
BEFORE you add things to the container to override.
If you look at the docs on how the new IServiceProviderFactory
stuff works, it's basically:
ConfigureServices
to register things with IServiceCollection
ContainerBuilder
is created and calls builder.Populate(services)
for youConfigureContainer
to register things with Autofac directlyIServiceProvider
In the older integration where you build the AutofacServiceProvider
yourself, you have to be careful about order. It's still Autofac - last in wins.
When you do services.AddControllersAsServices()
that is like saying builder.RegisterType<DataProcessController>().AsSelf()
... but it's not actually talking to the builder yet. It's like a "stored registration" that won't "play back" until you call Populate
.
Thus, if you simplify your registrations and put things in the order they actually execute:
// These will NOT be executed until builder.Populate()
// services.AddOptions();
// services.AddHttpClient();
// services.AddMvc()
// .AddControllersAsServices()
// .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var builder = new ContainerBuilder();
builder.RegisterInstance(_logger).As<ILogger>().SingleInstance();
builder.RegisterType<DataProcessController>().AsSelf()
.WithParameter("ratio", 0.4);
// Still not executed until builder.Populate()
services.AddSwaggerExamplesFromAssemblyOf<Startup>();
// Here goes everything!
// - AddOptions => ContainerBuilder
// - AddHttpClient => ContainerBuilder
// - AddMvc => ContainerBuilder
// - AddControllersAsServices => ContainerBuilder
// ---- OH NO! JUST OVERWROTE THE WITHPARAMETER REGISTRATION!
// - AddSwaggerExamplesFromAssemblyOf => ContainerBuilder
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
Here's what I'd recommend:
BEST CASE: MOVE TO THE SERVICE PROVIDER FACTORY SUPPORT
In the documentation, this is the one where in ASP.NET 1.1-2.2 you don't build the AutofacServiceProvider yourself and, instead, register Autofac at the web host level. This is the best option because it prepares you more for upgrade to ASP.NET Core 3.0 when you're ready. Once you hit 3.0 you'll have to do this anyway.
FALLBACK OPTION: ORGANIZE YOUR REGISTRATIONS
If you can't switch the integration to force you to use separate ConfigureServices
and ConfigureContainer
methods, at least organize the code in ConfigureServices
so you're ready. This will also ensure everything gets called in the right order to avoid this issue now and in the future.
services.AddWhatever()
registrations for IServiceCollection
at the top. For example, don't let services.AddSwaggerExamplesFromAssembly
linger down among Autofac registrations.ContainerBuilder
and immediately call Populate
. Do not put any registrations between those two things.builder.Register<T>()
registrations with ContainerBuilder
after the builder.Populate
call.