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);
}
...
});
}
Related
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>();
Although the error message seems obvious I believe it is different from other questions. As you can see in my Startup I have registered IBlobModelCache service.
public class Startup
{
...
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<DataContext>(options =>
{
options
.ReplaceService<IEntityMaterializerSource, Domain.EntityMaterializerSource>();
.UseSqlServer(
Configuration.GetConnectionString("Default"),
opt => opt.MigrationsAssembly("API")
);
});
services
.AddAuthentication(ConfigureAthentication)
.AddJwtBearer(ConfigureJwtBearer);
services
.AddLogging(ConfigureLogging)
.AddTransient<IBlobPersisterFactory, BlobPersisterFactory>()
.AddTransient<IBlobDbContextSavingHandler, BlobDbContextSavingHandler>()
.AddTransient<IBlobDbContextModelCreator, BlobDbContextModelCreator>()
.AddSingleton<IBlobModelCache, BlobModelCache>() // It is here
.AddOptions<DiskBlobStorageSettings>().Configure<IConfiguration>((settings, config) => config.Bind("BlobStorage", settings));
}
...
}
And here the constructor of my services so you can see that there is not any loop in them.
class EntityMaterializerSource : Base.EntityMaterializerSource
{
public EntityMaterializerSource(
[NotNull] EntityMaterializerSourceDependencies dependencies,
IBlobModelCache blobModelCache
)
: base(dependencies)
{
BlobModelCache = blobModelCache;
OnMaterializedMethod = typeof(EntityMaterializerSource)
.GetMethod(nameof(OnMaterialized), BindingFlags.Instance | BindingFlags.NonPublic);
}
}
class BlobModelCache : IBlobModelCache
{
// Has no constructor
}
The BlobModelCache is resolved in other services successfully and the error only happens when EF needs to materialize entities.
All I need is to be notified when an entity is materialized from DB and I could not find any solution but extending EntityMaterializerSource. I am using EF Core 3.1.8 and ASP.Net Core 3.1.
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;
}
}
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();
}
}
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>();
});
}