Passing IOptions<T> to method in StartUp Configuration - c#

I have set up my project to use the IOptions pattern for reading data from the appSettings file.
I have a class that has the following simple constructor to it:
public PlayClass(IOptions<MySettings> settings)
{
_settings = settings;
}
In my ConfigureServices method I have my config set up here:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MySettings>(options => Configuration.GetSection("MyOptions").Bind(options));
}
When I run or test this, everything works as expected. However, I need to call a method from my play PlayClass inside of ConfigureServices.
What is best way to achieve this?
I had originally thought it would be as simple as the following:
public void ConfigureServices(IServiceCollection services)
{
var x = services.Configure<BitBucketSettings>(options => Configuration.GetSection("BitBucketOptions").Bind(options));
var pc = new PlayClass(x);
pc.MyMethod();
}
But this only results in an error: cannot convert from IServiceCollection to IOptions<MySettings>

It is not clear why you want to create an object of class in Startup class. But you can solve your problem as following.
IServiceCollection is used only for create the dependency graph but to resolve the actual dependencies at runtime, ServiceProvider is needed.
To build ServiceProvider, BuildServiceProvider method needs to be called on ServiceCollection. You can see that in the code below.
public void ConfigureServices(IServiceCollection services)
{
//Register the configuration section in the service collection.
services.Configure<BitBucketSettings>(Configuration.GetSection("BitBucketOptions");
// Register the class in service collection.
services.AddScoped<PlayClass, PlayClass>();
// Build Service Provider
var sp = services.BuildServiceProvider();
// Resolve instance or PlayClass from service builder.
var pc = sp.GetService<PlayClass>();
// Call method on instance of PlayClass
pc.MyMethod();
}
I hope this will help you solve your issue.

Related

Sending telemetry to ApplicationInsights inside ConfigureServices

I'd like to send some telemetry from within ConfigureServices, for example if I'm missing a config setting I'd like to send an event to AI to track this.
public class Startup
{
public void ConfigureServices( IServiceCollection services)
{
services.AddApplicationInsightsTelemetry();
// Is there a way of getting the telemetry client here to send some telemetry?
// e.g. TelemetryClient.TrackEvent("MyEvent");
}
}
Is there any way of getting hold of the telemetry client created by AddApplicationInsightsTelemetry within ConfigureServices? Is the context even valid at that point?
No, we cannot get hold of the telemetry client created by AddApplicationInsightsTelemetry within ConfigureServices before ConfigureServices method is finished.
You should manually create a telemetry client for your purpose in ConfigureServices method, like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var s1 = services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault();
configuration.InstrumentationKey = Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
var telemetryClient = new TelemetryClient(configuration);
telemetryClient.TrackEvent("xxx");
}

Use Multiple Services for Singleton in ASP.NET Core 3.1

I am using the repository method for a data access class. So the constructor looks something like this:
public class MongoDbUnitOfWork : IMongoDbUnitOfWork
{
private readonly ILogger _logger;
private readonly IConfiguration _config;
public MongoDbUnitOfWork(ILogger logger, IConfiguration config)
{
_logger = logger;
_config = config
//do other stuff here, create database connection, etc.
}
}
public interface IMongoDbUnitOfWork
{
// various methods go in here
}
The key thing is that the constructor relies on the fact that 2 services are parsed to it.
Then in startup.cs I tried to do the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMongoDbUnitOfWork>(sp =>
{
var logger = sp.GetRequiredService<ILogger>();
var config = sp.GetRequiredService<IConfiguration>();
return new MongoDbUnitOfWork(logger, config);
});
//add other services
}
This compiled but did not work when I tried to run an API route through a controller. I got an error stating:
System.InvalidOperationException: Unable to resolve service for type 'NamespaceDetailsHere.IMongoDbUnitOfWork' while attempting to activate 'NamespaceDetailsHere.Controllersv1.TestController'.
Then I ran a little Debug.WriteLine() script in startup.cs to see whether the ILogger and IConfiguration services existed. They did. I'm not sure what I'm doing wrong here.
The ASP.NET Core service container will automatically resolve the services dependencies that are injected through the constructor, so you dont need the action configuration at all. When the service is constructed, any dependencies in the constructor are automatically required (as you're able to see with the exception).
Simply register your services with
services.AddSingleton<IMongoDbUnitOfWork, MongoDbUnitOfWork>();

Mapster Global Configuration with Dependency Injection

I'd like to know if there is a way to globally configure Mapster while using Dependency Injection?
The configuration options appear to be for the static usage and also for a singleton pattern only.
Mapster Configuration
Mapster Dependency Injection
I have created an extension method.
// Extension method
public static IServiceCollection AddMapster(this IServiceCollection services, Action<TypeAdapterConfig> options = null)
{
var config = new TypeAdapterConfig();
config.Scan(Assembly.GetAssembly(typeof(Startup)));
options?.Invoke(config);
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
return services;
}
// Called in Startup.ConfigureServices(IServiceCollection services)
services.AddMapster(options =>
{
options.Default.IgnoreNonMapped(true); // Does not work.
TypeAdapterConfig.GlobalSettings.Default.IgnoreNonMapped(true); // Does not work.
});
I imagine these don't work because the ServiceMapper is creating its own instance without using anything I've configured.
I implemented Mapster in a Blazor Server application, and I struggled to find documentation on how to scan the assembly for mapping registrations.
I have a class in my application that implements the IRegister interface and defines the mappings
public class MappingRegistration : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<ModelA, ModelB>();
}
}
In the ConfigureServices of the Startup.cs I have this then
var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
// scans the assembly and gets the IRegister, adding the registration to the TypeAdapterConfig
typeAdapterConfig.Scan(Assembly.GetExecutingAssembly());
// register the mapper as Singleton service for my application
var mapperConfig = new Mapper(typeAdapterConfig);
services.AddSingleton<IMapper>(mapperConfig);
I hope this can save someone's time. If anybody is aware of better ways, please let me know.
You can change from
var config = new TypeAdapterConfig();
to
var config = TypeAdapterConfig.GlobalSettings;

Multiple hosts with the same DI container

In C#.NET Core you can create a generic host using the following code:
IHostBuilder builder = new HostBuilder()
.ConfigureServices((context, collection) => {
collection.AddSingleton<IMyClass, MyClass>();
collection.AddHostedService<MyService>();
});
await builder.RunConsoleAsync();
This creates a new instance of MyService with the default DI container.
Now, say that I want to create a new host inside MyService. This is easy enough (a web host in this case):
IWebHost webHost = WebHost.CreateDefaultBuilder()
.UseStartup<MyStartup>()
.Build();
.RunAsync();
This webhost will have its own Dependency Injection container, so it will not have access to all dependencies I've already added to the generic host container: i.e. it will not be able to have IMyClass injected into MyStartup.
I've also tried adding a custom IServiceProviderFactory<> using the following code (based on the .UseDefaultServiceProvider() code where they use IServiceCollection as the builder type):
public class CustomServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly IServiceProvider _provider;
public CustomServiceProviderFactory(IServiceProvider provider)
{
_provider = provider;
}
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
return _provider;
}
}
Then in my HostBuilder I added it through .UseServiceProviderFactory(new CustomServiceProviderFactory(_serviceProvider)), but for some reason the HostedService is instantiated before this is created, causing DI exceptions about not finding the required objects.
However, seeing as WebHost.CreateDefaultBuilder() is now the preferred way to create a webhost (in .NET Core 3.0), and an IWebHostBuilder does not have an option to set a custom IServiceProviderFactory this does seem like a dead end.
How can I have the webhost use the same DI container as the initial generic host?
I've tried to do the same thing and this is what I have landed on. Not fully tested but it does appear to work.
First, in my base/first HostBuilder, add the service collection as a service so an IServiceCollection can be resolved via DI later on.
IHostBuilder builder = new HostBuilder()
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<IMyService, MyService>();
services.AddHostedService<MyApp>();
services.AddSingleton(services);
});
In IHostedService.StartAsync() I create the WebHost. I copied the use of services.Replace from the functionality inside UseDefaultServiceProvider():
IWebHost host = WebHost
.CreateDefaultBuilder()
.ConfigureServices(services =>
{
var options = new ServiceProviderOptions();
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new CustomServiceProviderFactory(_services, options)));
})
.UseStartup<MyStartup>()
.Build();
In the constructor of my CustomServicesProvider, I also need to remove any IHostedService services or else it appears you enter an infinite loop of the service starting. When creating the service provider, I add everything from the constructor-passed service collection to the local service collection.
class CustomServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly IServiceCollection _baseServices;
private readonly ServiceProviderOptions _options;
public CustomServiceProviderFactory(IServiceCollection baseServices, ServiceProviderOptions options)
{
_baseServices = baseServices;
_options = options;
_baseServices.RemoveAll<IHostedService>();
}
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
foreach (var service in _baseServices)
{
containerBuilder.Add(service);
}
return containerBuilder.BuildServiceProvider(_options);
}
}
I was then able to create a Controller after adding app.UseRouting() and app.UseEndpoints(...) in my startup class. Injecting IMyService was successfully resolved and I could use it as normal.
You could also test it by just adding app.ApplicationServices.GetRequiredService<IMyService>() in your Startup.Configure() method and see that the correct service is returned.

AddHttpContextAccessor in ConfigureServices vs per HttpClient

Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// FIRST VERSION
services.AddHttpContextAccessor();
// SECOND VERSION
var myService1 = services.AddHttpClient<TestHttpClient1>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient1"]);
});
myService1.Services.AddHttpContextAccessor();
var myService2 = services.AddHttpClient<TestHttpClient2>(c =>
{
c.BaseAddress = new Uri(Configuration["TestHttpClient2"]);
});
myService2.Services.AddHttpContextAccessor();
}
My guess would be to think that in the second version, we have two singleton, one will be used for class TestHttpClient1 and the other one for TestHttpClient2 but I dont see why we do that because I saw this code in production.
Is there any difference between adding the httpContextAccessor one time in ConfigureServices method versus adding the HttpContextAccessor per HttpClient configured.
No, there's no difference whatsoever. myService1.Services and myService2.Services both reference the same IServiceCollection as the services variable. The first call (services.AddHttpContextAccessor()) will register the service, but the next two calls (myService1.Services.AddHttpContextAccessor() and myService2.Services.AddHttpContextAccessor()) will no-op (do nothing).
To put that all in to context, here's an extract from the source code for AddHttpClient<TClient>(...) (source):
var builder = new DefaultHttpClientBuilder(services, name);
// ...
return builder;
A new instance of DefaultHttpClientBuilder is created, which wraps up the IServiceCollection that's passed in. As this is an extension method, services here refers to the same services as in your ConfigureServices method. This is then exposed through IHttpClientBuilder.Services, which is what you're using when you reference e.g. myService1.Services.
The call to AddHttpContextAccessor uses TryAddSingleton, which will register the service only if it hasn't already been registered (source):
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your example, it has already been registered by that first call to services.AddHttpContextAccessor(), which means the next two registration attempts do nothing.

Categories

Resources