Jonas
Jonas

Reputation: 3283

How to conditionally disable Application Insights in ASP.NET Core?

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

Upvotes: 1

Views: 1401

Answers (2)

JimiSweden
JimiSweden

Reputation: 814

For reference if anyone is looking for disabling ApplicationInsights in a blazor app where you inject the JavascriptSnippet in cshtml files

  1. make sure you use the interface in the injections; i.e IJavascriptSnippet, not JavascriptSnippet.
  2. Add a fake class implementing IJavascriptSnippet
  3. use conditional adding for your ApplicationInsights services

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

Nkosi
Nkosi

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

Related Questions