Reputation: 3283
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.
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.
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
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
Upvotes: 1
Views: 1401
Reputation: 814
For reference if anyone is looking for disabling ApplicationInsights in a blazor app where you inject the JavascriptSnippet in cshtml files
In Startup.cs (note that Environemt.Local is not built in, its just returning string "Local"
private readonly IWebHostEnvironment _webHostingEnvironment;
...
if (_webHostingEnvironment.IsEnvironment(Environment.Local))
{
//needed since Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet is injected in some cshtml-pages.
services.AddSingleton<IJavaScriptSnippet,FakeJavascriptSnippet>();
}
else
{
AddOurImplementationForApplicationInsights(services);
}
...
}
public class FakeJavascriptSnippet : IJavaScriptSnippet
{
public string FullScript { get => string.Empty; }
}
Searchwords: "Conditionally add ApplicationInsights in local development" "How to not load ApplicationInsights in development environment" "C# Blazor ApplicationInsights" "@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet" "@inject Microsoft.ApplicationInsights.AspNetCore.IJavaScriptSnippet JavaScriptSnippet"
Upvotes: 0
Reputation: 247008
In the basic example, there is no need to build a service provider. It is even advised against in most documentation. The desired settings can be extracted directly from configuration.
public void ConfigureService(IServiceCollection services) {
var section = configuration.GetSection(nameof(AppSettings));
// Configure the services for IOptions injection
services.Configure<AppSettings>(section);
// Extract the settings from configuration explicitly as needed
AppSettings appSettings = section.Get<AppSettings>();
// Conditionally include the service using the settings
if (appSettings.EnableApplicationInsights) {
services.AddApplicationInsightsTelemetry();
}
//...
}
There really is no need to involve dependent classes or use a custom IConfigureOptions<TOptions>
to satisfy the desired conditional in Startup
Reference: Configuration in ASP.NET Core - Bind to an object graph
Upvotes: 2