I'm trying to avoid the service locator pattern in ASP.NET Core when conditionally including Application Insights in my ASP.NET Core application, the reason for this is I want to completely disable Applications Insights during development.
Service locator pattern (not recommended)
The most basic way of doing this is to service locate a IOptions
setting in ConfigureServices()
after building a service provider using the BuildServiceProvider()
method on the IServiceCollection
Example of (not recommended!) service locator pattern:
public void ConfigureService(IServiceCollection services)
{
// Configure the services
services.Configure<AppSettings>(configuration.GetSection(nameof(AppSettings)));
// Build an intermediate service provider
var sp = services.BuildServiceProvider();
// Resolve the settings from the service provider;
var appSettings = sp.GetRequiredService<AppSettings>();
// Conditionally include the service using the settings
if (appSettings.EnableApplicationInsights) {
services.AddApplicationInsightsTelemetry();
}
}
This is not a recommended pattern as it results in an additional copy of singleton services being created. But we can be sure Application Insights is completely disabled in the application, in fact it's not even included in the DI container.
Better pattern #1
A much better way of resolving classes that are dependent on other services is to use the AddXXX overload that provides you with the IServiceProvider. This way you do not need to instantiate an intermediate service provider.
The following samples show how you can use this overload in AddSingleton/AddTransient methods.
services.AddSingleton(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<AppSettings>();
var fooService = new FooService();
fooService.Enable = settings.EnableFooService
return fooService;
});
services.AddTransient(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<AppSettings>();
var barService = new BarService();
barService.Enable = settings.EnableBarService
return barService;
});
The overload with IServiceProvider is available for i.e. AddSingleton, AddScoped, AddTransient. This pattern works great and is simple to implement, but often services have AddFoo()
methods that do not provide this overload, i.e. AddApplicationInsightsTelemetry, AddCors, AddAuthentication, AddAuthorization...
I got inspiration from Ehsan Mirsaeedi answer: https://stackoverflow.com/a/56278027/294242
Better pattern #2
We can implement the IConfigureOptions<TOptions>
interface, register our configuration class in the
ConfigureServices
method.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigureOptions<ApplicationInsightsServiceOptions>, ConfigureApplicationInsightsServiceOptions>();
services.AddApplicationInsightsTelemetry();
}
public class ConfigureApplicationInsightsServiceOptions : IConfigureOptions<ApplicationInsightsServiceOptions>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConfigureApplicationInsightsServiceOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(ApplicationInsightsServiceOptions options)
{
using var scope = _serviceScopeFactory.CreateScope();
var provider = scope.ServiceProvider;
var settings = provider.GetRequiredService<AppSettings>();
if (!settings.EnableTracking)
{
options.EnableQuickPulseMetricStream = false;
options.EnablePerformanceCounterCollectionModule = false;
options.EnableAppServicesHeartbeatTelemetryModule = false;
options.EnableAzureInstanceMetadataTelemetryModule = false;
options.EnableDependencyTrackingTelemetryModule = false;
options.EnableEventCounterCollectionModule = false;
options.EnableAdaptiveSampling = false;
options.EnableDebugLogger = false;
options.EnableHeartbeat = false;
options.EnableRequestTrackingTelemetryModule = false;
options.EnableAuthenticationTrackingJavaScript = false;
options.EnableDiagnosticsTelemetryModule = false;
}
}
}
I'm currently evaluating this pattern but i'm not sure Application Insights is completely disabled in my application.
I got inspiration from: https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer