Search code examples
azureasp.net-coreazure-application-insightsasp.net-core-2.2scrutor

Filling User Id Field in Application Insights from ASP.NET Core


I would like to be able to populate the User Id field in Application Insights with my real username data. This is an internal application, so privacy concerns with a simple username field are moot.

As far as I can tell, all solutions available online for this strictly work in .NET Framework, not .NET Core.

You can find this solution in a few places, including some old AI documentation on GitHub. However, when I run it, I get an error on startup indicating that dependency on the scoped object IHttpContextAccessor is not acceptable from a singleton, which of course is logical. I don't see how this could have ever worked unless a previous version of .NET Core's DI allowed it (I'm on 2.2).

This issue on GitHub sort of spells out the problem but it was closed after the AI team pointed out that you must use a singleton. I tried variations of what's in the OP and the first response, and while the code ran, the User Id field on AI continued to be filled with gibberish data.

Is there any way to make the User Id field in AI fill with something useful for server requests coming from an ASP.NET Core app?

EDIT

After seeing the answer below that my code should have worked just fine, I went back and realized the exception message hadn't specifically mentioned IHttpContextAccessor:

System.InvalidOperationException: 'Cannot consume scoped service 'Microsoft.ApplicationInsights.Extensibility.ITelemetryInitializer' from singleton 'Microsoft.Extensions.Options.IConfigureOptions`1[Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]'.'

Now, my code looks just about identical to @PeterBons' answer below, so on the face of it this exception made no sense. TelemetryConfiguration doesn't event appear in my code. But then I remembered I am using Scrutor to lazily do DI registration in Startup:

    services.Scan(scan => scan
        .FromAssembliesOf(typeof(Startup), typeof(MyDbContext))
        .AddClasses()
        .AsSelfWithInterfaces().WithScopedLifetime());

I assume the problem was that I need to register my ITelemetryInitializer as Singleton, and this code was inadvertently de- or re-registering it as scoped. So I changed the last two lines to:

        .AddClasses(f => f.Where(t => t != typeof(RealUserAIProvider)))
        .AsSelfWithInterfaces().WithScopedLifetime());

And it worked. Rather than editing out my mistake above, I'll leave it. @PeterBons' answer below is still going to be helpful to other people, and maybe my confusion with Scrutor will help someone too.


Solution

  • You don't have to register the HttpContextAccessor as a scoped dependency. Just use a singleton.

    We have this working in production using this:

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    

    combined with this initializer:

    public class SessionDetailsTelemetryEnrichment : TelemetryInitializerBase
    {
        public SessionDetailsTelemetryEnrichment(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
        {
    
        }
    
        protected override void OnInitializeTelemetry(HttpContext platformContext, RequestTelemetry requestTelemetry, ITelemetry telemetry)
        {
            telemetry.Context.User.AuthenticatedUserId =
                platformContext.User?.Claims.FirstOrDefault(c => c.Type == "Username")?.Value ?? string.Empty;
    
        }
    }