How to validate all registered types in ASP.NET Core DI? - asp.net-core

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

Related

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 configure an external SOAP service in a net core 2.2 project

I have a project developed in .net core 2.2.
You need to use a WCF in .Net Framework.
The inclusion of WCF and Reference.cs went well. The service is Ok, as a connected service.
However I am getting a dependency injection error:
microsoft.extensions.dependencyinjection.servicelookup.callsite factory.create argument callsite(type servicetype, type implementationtype, callsitechain callsitechain, parameterinfo[] parameters, bool throwifcallsitenotfound)
Performing searches on the internet, some answers were related to the configuration of Startup.cs and Program.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddSessionStateTempDataProvider()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = this.GetJsonSerializerContractResolver();
options.SerializerSettings.DateFormatString = this.GetJsonSerializerDateFormatString();
});
this.ConfigureIoC(services);
}
private void ConfigureIoC(IServiceCollection services)
{
this.ConfigureHelpers(services);
this.ConfigureUnitOfWorks(services);
this.ConfigureFacades(services);
this.ConfigureBusinessServices(services);
this.ConfigureServiceFilters(services);
this.ConfigureRestConsumers(services);
}
private void ConfigureHelpers(IServiceCollection services)
{
services.AddScoped(typeof(IHpUrl), typeof(HpUrl));
services.AddScoped(typeof(IHpArvoreProduto), typeof(HpArvoreProduto));
}
My doubt is whether I would need only one method:
private void ConfigureSoaps(IServiceCollection services)
{
services.AddScoped(typeof(ISoapBla), typeof(SoapBla));
}
Since in some responses from the Web, the use of SoapCore among other solutions has been suggested.

Service Injected on startup is null in Extension Service Configuration aspnet core

Service Injected on startup is null in Extension Service Configuration ASP.NET Core
We have one service for userservice to save user profiles and it is injected as scoped on startup.
In our extension, we add another service for students as singleton to insert update delete users' transactions. We want student info from userservice but in our student service it showing null.
In Startup.
services.AddMemoryCache();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IUserService, UserService>();
In Extension
public void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
{
serviceCollection.AddMvc();
serviceCollection.AddSingleton<IStudenService, StudenService>();
}
In Student Service
public class StudentSerivce : IStudentSerivce
{
private readonly IUserService _userService;
public StudentSerivce(IUserService userService)
{
_userService = userService; // is null
}
}
You could try this:
public StudentService(IServiceProvider services)
{
Services = services;
}
public IServiceProvider Services { get; }
public void SomeMethod()
{
using (var scope = Services.CreateScope())
{
var userService = scope.ServiceProvider.GetRequiredService<IUserService>();
// Do Something
}
}
It would probably be better to make them both scoped or both singletons (then it would work without manually creating a scope).
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Reference: Dependency injection in ASP.NET Core

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.

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