ConfigureContainer in .NET Core 3.1 Generic Host implementation - asp.net-core

I am trying to migrate our framework project to .NET Core 3.1.
As part of the migration, I am trying to register modules via ConfigureContainer method provided by the GenericHost.
This is what I have:
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new
WorkerServiceDependencyResolver.WorkerServiceDependencyResolver()))
And my WorkerServiceDependencyResolver has the following:
builder.RegisterModule(new FirstModule());
builder.RegisterModule(new SecondModule());
But when I do it this way, my application doesn't run, it starts without any error, but doesn't do anything.
But If I write it this way (this is how we had in .NET Framework):
var builder = new ContainerBuilder();
builder.RegisterModule(new FirstModule());
builder.RegisterModule(new SecondModule());
_container = builder.Build();
Everything works as expected when I explicitly build the container, but my understanding was that we do not need that in .NET Core?
Any inputs are greatly appreciated.
Cheers!

By specifying to the IHostBuilder that the Service Provider Factory is an AutofacServiceProviderFactory, it allows you create to a method right inside your Startup class, called ConfigureContainer which takes the ContainerBuilder as a parameter.
This is how you would instantiate the IHostBuilder. Nothing fancy, just following the ASP NET Core guide.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Then, in the Startup class, add that method.
public class Startup
{
public Startup(IConfiguration configuration) => Configuration = configuration;
public IConfiguration Configuration { get; }
// ...
public void ConfigureContainer(ContainerBuilder builder)
{
// Here you register your dependencies
builder.RegisterModule<FirstModule>();
}
// ...
}
Then, your IContainer will be the IOC Container of your ASPNET Core application scope.
Notice than the builder is not being built at any time. This will be done by the IHostBuilder at some point (making use of the Autofac extension).
Hope that helps.

Related

ASP.NET core start dependency injection at when app is started

I am injecting one of my services as last item in the ConfigureServices method:
services.AddSingleton<IBot>(_ => new De.Impl.Bot(Configuration));
I am running the app in docker container so whenever container is restarted I need to invoke my controller so I can get my service running. How can I get it running in the beginning of the configuration part?
As for .NET Core 3.1 and .NET 5.0
First, you can write your business code as an extension:
using Microsoft.Extensions.Hosting;
public static IHost DoSomething(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var yourService = services.GetService<YourService>();
yourService.DoSomething();
}
return host;
}
So if you want to invoke that method every time your application starts, simply call:
// .NET 5.0 Style
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.DoSomething()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}
As for .NET 6.0
It's gonna be easier. Simply access var yourService = app.Services.GetRequiredService<YourService> in the Program.cs.
// .NET 6.0 style, in Program.cs, before app.Run();
var yourService = app.Services.GetRequiredService<YourService>();
yourService.DoSomething();
app.Run();

How to make Log4Net work with ASP.NET Core 3.1 using Dependency Injection?

I am trying to use log4Net in an ASP.NET Core 3.1 application and I am trying to get it to work with Dependency Injection using controllers. I am successfully able to use Log4Net using LogManager.GetLogger(type). Any help would be appreciated.
Here is a sample of the code I am using for logging now:
public class HomeController : Controller
{
private static readonly log4net.ILog _log = LogManager.GetLogger(typeof(Logger));
public HomeController()
{
_log.Debug("Test");
}
}
Any services you want to inject into a Controller must be registered in Startup.ConfigureServices(IServiceCollection) (Startup.cs), or in Program.CreateHostBuilder(string []) (Program.cs) usually after CreateDefaultBuilder and ConfigurWebHostDefaults.
You can register the service using the following:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => /* ... */)
.ConfigureLogging(builder =>
{ // Configuration here:
builder.SetMinimumLevel(LogLevel.Trace);
builder.AddLog4Net("log4net.config");
});
Then, you can inject using the Microsoft.Extensions.Logging.ILogger<T> (where T is the class being logged), which will use Log4Net:
public class HomeController : Controller {
private readonly ILogger<HomeController> _log;
// A suitable logger instance is created by the runtime DI:
public HomeController(ILogger<HomeController> log)
{
_log = log;
_log.Debug("Test");
}
}
For more information, here is a tutorial.

'ConfigureServices returning an System.IServiceProvider isn't supported.' in .NET Core 3.1 using Autofac

I want to use autofac injection instead of default .net core solution.
Here is my startup file :
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc(option => option.EnableEndpointRouting = false) ;
var cb = new ContainerBuilder();
cb.RegisterModule<mydependecymodule>();
cb.Populate(services);
var container = cb.Build();
return new AutofacServiceProvider(container);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes=>routes.MapRoute("default","/{controller=home}/{action=index}"));
}
And here is my program.cs
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).UseServiceProviderFactory(new AutofacServiceProviderFactory());
But when I run my application I get this error in my main method:
System.NotSupportedException: 'ConfigureServices returning an System.IServiceProvider isn't supported.'
In ASP.NET Core 3.0 the ASP.NET Core hosting model changed and you can't return an IServiceProvider anymore. This is documented in the Autofac docs for integrating with ASP.NET Core 3.0+.
You have to switch your ConfigureServices to be void, and if you want to register stuff directly with Autofac you need to use ConfigureContainer. You also need to register the AutofacServiceProviderFactory in your Program.Main method when you construct the host. There are examples in the documentation showing how to do this.

Options<T> not populating in DI

I'm using .Net Core 2.1 and an Aggregate / Facade pattern for my dependencies (which I happily do elsewhere using Ninject / .net 4.6). But when I try to pass through options I get a null (Debugging I can see there being picked up) but there not passed to Autofac (I'm fairly sure its my as they weren't when I tried Ninject either).
I've made a simple test project (new .net core web application /2.1) and then added a minimal amount of code to replicate
Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ApiEndpointsConfiguration>(Configuration);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Create the container builder.
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterAggregateService<IViewModelProvider>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("TEST")).ToArray();
builder.RegisterAssemblyTypes(assemblies)
.Where(t => t.IsClass)
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAggregateService<IDomainServiceProvider>();
ApplicationContainer = builder.Build();
var chkOptions = ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(ApplicationContainer);
}
Program.cs
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddAutofac())
.UseStartup<Startup>();
}
IViewModelProvider.cs
public interface IViewModelProvider
{
IProgrammeViewModelBuilder ProgrammeViewModel { get; }
}
IProgrammeViewModelBuilder.cs
public interface IProgrammeViewModelBuilder
{
ProgrammeViewModel GetProgrammeViewModel();
}
My initial issue was that in my service, controller calls the injected viewmodelbuilder
var viewModel = _viewModels.ProgrammeViewModel.GetProgrammeViewModel();
which in turn calls the service -
readonly IOptions<ApiEndpointsConfiguration> _apiSettings;
public ProgrammeService(IOptions<ApiEndpointsConfiguration> apiSettings) : base (new Uri(apiSettings.Value.BaseAddress))
{
_apiSettings = apiSettings;
}
but at that point (the constructor firing) the service configuration items were null so I've stepped through and I can see that services has the values for "ApiEndpointsConfiguration" picked up but when they get passed through to the "builder" the values are null
ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
shows null for the values inside.
Not sure what it is I'm doing wrong?
:( Truly this is when the answer is so much simpler thank it looks. Kudos to anyone who spots it;
services.Configure<ApiEndpointsConfiguration>(Configuration.GetSection("ApiEndpointsConfiguration"));
rather than
services.Configure<ApiEndpointsConfiguration>(Configuration);
So essentially whilst I thought I could see it debugging I was seeing the raw JSON provided values not the "configured service". I'll leave this here as a lesson to myself to check the simple things first.
Not sure what what was actually being "registered" in my first effort.

Equivalent of Configure<T> using autofac modules

What is the equivalent to the method Configure<TOptions> of the OptionsConfigurationServiceCollectionExtensions when using Autofac modules?
My ConfigureServices method looks like this, but I want to move the services.Configure<MyOptions>(Configuration.GetSection("MyOptions")) to MyModule.
public IServiceProvider ConfigureServices(IServiceCollection services) {
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
containerBuilder.RegisterModule<MyModule>();
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
How does the registration look like in the Load-method of the Module
protected override void Load(ContainerBuilder builder)
{
// configure options here
}
I'm not familiar with Autofac personally, but generally speaking, all Configure<T> does is 1) bind a particular configuration section to a class and 2) register that class with the service collection, so it can be injected directly.
As a result, you can instead use the following to bind your strongly-typed configuration:
var config = config.GetSection("MyOptions").Get<MyOptions>();
And, then you'd simply register that with Autofac as a constant in singleton-scope.
I recently encountered this same issue, I implemented the following so that you can still use IOptions, IOptionsMonitor and IOptionsSnapshot, but register the configuration from the AutoFac Module.
The prerequisite is that you call services.AddOptions() in ConfigureServices method:
var sfConfig = _configuration.GetSection("MyOptions");
builder.Register(ctx => new ConfigurationChangeTokenSource<MyOptions>(Options.DefaultName, sfConfig))
.As<IOptionsChangeTokenSource<MyOptions>>()
.SingleInstance();
builder.Register(ctx => new NamedConfigureFromConfigurationOptions<MyOptions>(Options.DefaultName, sfConfig, _ => { }))
.As<IConfigureOptions<MyOptions>>()
.SingleInstance();
This requires that you run services.AddOptions() within the ConfigureServices method.
In the example above, "MyOptions" is the section name in your configuration, and MyOptions type is the POCO class that has the fields to hold the result.
This is basically a conversion of what microsoft has here: https://github.com/aspnet/Options/blob/master/src/Microsoft.Extensions.Options.ConfigurationExtensions/OptionsConfigurationServiceCollectionExtensions.cs
Startup.cs
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.RegisterModule(new AutofacModule(Configuration));
}
AutofacModule.cs
public class AutofacModule: Module
{
private IConfiguration configuration;
public AutofacModule(IConfiguration configuration)
{
this.configuration = configuration;
}
protected override void Load(ContainerBuilder builder)
{
builder.Register(p => configuration.GetSection("AppAPIKey").Get<ConfigSettings>()).SingleInstance();
builder.RegisterType<TestService>()
.As<ITestService>()
.SingleInstance();
}
}