Equivalent of Configure<T> using autofac modules - asp.net-core

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

Related

Is it possible to add all IHostedService classes with a loop (ASP.NET Core 6)?

Is it possible to add all IHostedService implemented classes in a loop without adding them individually in ASP.NET Core 6?
Let's say we have this two implementations:
public class FirstImplementationOfHostedService : IHostedService
{
// ...
}
public class SecondImplementationOfHostedService : IHostedService
{
// ...
}
The default way in Program.cs to add them is:
builder.Services.AddHostedService<FirstImplementationOfHostedService>();
builder.Services.AddHostedService<SecondImplementationOfHostedService>();
But, what about having a hundred implementations?
There has to be a better way to add (at runtime) the one hundred implementations in Program.cs without explicitly spelling out all their names!
You can use an nuget package like this or you can create an extension method and get all references of services with reflection:
public static class ServiceCollectionExtensions
{
public static void RegisterAllTypes<T>(this IServiceCollection services,
Assembly[] assemblies,
ServiceLifetime lifetime = ServiceLifetime.Transient)
{
var typesFromAssemblies = assemblies.SelectMany(a =>
a.DefinedTypes.Where(x => x.GetInterfaces().Contains(typeof(T))));
foreach (var type in typesFromAssemblies)
services.Add(new ServiceDescriptor(typeof(T), type, lifetime));
}
}
and than call it at startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ....
services.RegisterAllTypes<IInvoicingService>(new[] { typeof(Startup).Assembly });
}
But be careful , you are registering services in a collection. There is a long version of answer here. You should check.
You can use scrutor which does assembly scanning (which it seems like what you want) https://andrewlock.net/using-scrutor-to-automatically-register-your-services-with-the-asp-net-core-di-container/
The answer of #nzrytmn totally worked. Thank you very much!
I just made a few tweaks in RegisterAllTypes to fulfill my own requirements:
public static void RegisterAllTypes<T>(this IServiceCollection services)
{
var assemblies = new[] { Assembly.GetEntryAssembly() };
var typesFromAssemblies = assemblies.SelectMany(a => a?.DefinedTypes.Where(x => x.GetInterfaces().Contains(typeof(T))));
foreach (var type in typesFromAssemblies)
services.Add(new ServiceDescriptor(typeof(T), type, ServiceLifetime.Singleton));
}
Then in Program.cs:
builder.Services.RegisterAllTypes<IHostedService>();

How to access Configuration within an IHostingStartup implementation?

Trying to get access to IConfiguration from within an IHostingStartup implementation and constructor injection is not supported.
The normal Startup.cs implementation allows for IConfiguration to be injected making it easy to access from within ConfigureService() and Configure() methods.
What is the best practice way for gaining access to configuration?
There is an overload on some builder methods (i.e. ConfigureLogging() ConfigureAppConfiguration() ConfigureServices() that allows for a WebHostBuilderContext to be passed into the Action.
The WebHostBuilderContext provides access to the HostingEnvironment and Configuration
public class HostingStartupConfiguration : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder
.ConfigureLogging((context, builder) =>
{
// clear providers set from host application
if (context.HostingEnvironment.IsDevelopment())
{
...
}
})
.ConfigureAppConfiguration((context, builder) =>
{
if (context.HostingEnvironment.IsDevelopment())
Console.WriteLine("we are in dev mode");
...
})
.ConfigureServices((context, services) =>
{
...
// get assemblies based on configuration to load as Application Parts
var assemblies = GetControllerAssemblies(context.Configuration);
// register controllers application parts from external assemblies
foreach (var assembly in assemblies)
{
builder.AddApplicationPart(assembly);
}
...
});
}

How to write an extension method that allows you to set options without creating the options instance

I really like the pattern where I can configure a service through an option class without having to create it, but I can't find an example of how to write an extension method that allows me to use that same pattern such as the one below that exists for registering a DbContext.
services.AddDbContext<MyDbContext>(options => options.EnableDetailedErrors());
I can see the method signature uses an action method, but I can't seem to find the extension class in GitHub for ASP.NET Core that shows me how to write an extension method using that type of option builder pattern.
For example, take the following service code. How would I write the extension method so that I could configure the options during service registration.
public void ConfigureServices(IServiceCollection services)
{
services.AddMyService(options => options.SomeSetting = true);
}
public interface IMyService
{
void DoSomething();
}
public class MyService : IMyService
{
private readonly MyServiceOptions _options;
public MyService(IOptions<MyServiceOptions> options)
{
_options = options.Value;
}
public void DoSomething()
{
Console.WriteLine(_options.SomeSetting);
}
}
public static class MyServiceExtensions
{
// How would I write this extension method so that I could configure it with options overload
public static IServiceCollection AddMyService(this IServiceCollection services, Action<MyServiceOptions> configure)
{
services.AddSingleton<IMyService, MyService>();
return services;
}
}
ASP.NET Core provides this mechanism with the IConfigureOptions
interface. You implement this interface in a configuration class and
use it to configure the IOptions object in any way you need.
It's as easy as:
public class MyServiceConfiguration : IConfigureOptions<MyServiceOptions>
{
private MyServiceOptions _options;
public MyServiceConfiguration(IOptions<MyServiceOptions> options)
{
_options = options.Value;
}
public void Configure(MyServiceOptions options)
{
options.SomeSetting = _options.SomeSetting;
options.SomeOtherSetting = _options.SomeOtherSetting;
}
}
All that remains is to register this implementation in the DI container.:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyServiceOptions>(options => options.SomeOtherSetting = true);
services.AddSingleton<IMyService, MyService>();
}
With this configuration, when IOptions is injected into your service, the MyServiceOptions object will be configured by the ConfigureMyServiceOptions class.
Be careful! The ConfigureMyServiceOptions object is registered as a singleton,
so it will capture any injected services of scoped or transient lifetimes.

How to validate all registered types in ASP.NET Core DI?

I want to check that the type registrations I established in Startup.cs are all valid at runtime (either when starting up the service or as part of a test suite). There's a feature like this in Lamar and other containers.
ASP.NET Core 3.x actually introduced a feature for scope and provider validation. Both of these are useful in different contexts (see the below post and sample code).
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
options.ValidateOnBuild = true;
});
https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
You can iterate the services that you want to validate, and try to initialize a service with GetRequiredService<T>. It will throw an exception if there is something wrong. More info on
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions.getrequiredservice?view=aspnetcore-2.2
The IServiceCollection is actually enumerable over ServiceDescriptor which contains type information on the registered service and implementation. The service collection isn't usually registered, but it should be possible to capture both the service collection and service provider in a hosted service.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddServicesValidation();
}
The right extension method can capture the services collection without actually registering it (which feels "safer").
public static class ValidateServicesExtensions
{
public static IServiceCollection AddServicesValidation(this IServiceCollection services)
{
services.AddHostedService<ValidateServices>(provider => new ValidateServices(services, provider));
return services;
}
}
Now, the hosted service can iterate over the registered services & implementations. Although, this code bombs on the first generic IOptions<TOption>, but I'm sure we can figure something out?
ValidateServices.cs
public class ValidateServices : BackgroundService
{
private readonly IServiceCollection services;
private readonly IServiceProvider provider;
public ValidateServices(
IServiceCollection services,
IServiceProvider provider
)
{
this.services = services;
this.provider = provider;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = provider.CreateScope();
foreach (var service in services)
{
_ = scope.ServiceProvider.GetRequiredService(service.ServiceType);
}
return Task.CompletedTask;
}
}

Set dummy IP address in integration test with Asp.Net Core TestServer

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā€ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}