diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index 8963b77b..6d7e1f18 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvaluationDataToApplicationInsights", "examples\EvaluationDataToApplicationInsights\EvaluationDataToApplicationInsights.csproj", "{1502529E-47E9-4306-98C4-BF6CF7C7C275}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj", "{C647611B-A8E5-4AD7-9DBA-60DDE276644B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,10 @@ Global {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Debug|Any CPU.Build.0 = Debug|Any CPU {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.ActiveCfg = Release|Any CPU {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.Build.0 = Release|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs new file mode 100644 index 00000000..8dd2378f --- /dev/null +++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.FeatureManagement.FeatureFilters; +using System; +using System.Threading.Tasks; + +namespace Microsoft.FeatureManagement +{ + /// + /// Used to add targeting information to HTTP context. + /// + public class TargetingHttpContextMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId"; + + /// + /// Creates an instance of the TargetingHttpContextMiddleware + /// + public TargetingHttpContextMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); + } + + /// + /// Adds targeting information to the HTTP context. + /// + /// The to add the targeting information to. + /// The to retrieve the targeting information from. + /// Thrown if the provided context or targetingContextAccessor is null. + public async Task InvokeAsync(HttpContext context, ITargetingContextAccessor targetingContextAccessor) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (targetingContextAccessor == null) + { + throw new ArgumentNullException(nameof(targetingContextAccessor)); + } + + TargetingContext targetingContext = await targetingContextAccessor.GetContextAsync().ConfigureAwait(false); + + if (targetingContext != null) + { + context.Items[TargetingIdKey] = targetingContext.UserId; + } + else + { + _logger.LogDebug("The targeting context accessor returned a null TargetingContext"); + } + + await _next(context); + } + } +} diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj new file mode 100644 index 00000000..2d8e6d4f --- /dev/null +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj @@ -0,0 +1,51 @@ + + + + + + 4 + 0 + 0 + -preview + + + + + + net6.0 + enable + true + false + ..\..\build\Microsoft.FeatureManagement.snk + + + + Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore provides a solution for tagging Application Insights telemetry with Microsoft.FeatureManagement targeting information. + Microsoft + Microsoft + https://licenses.nuget.org/MIT + https://github.com/Azure/AppConfiguration + Release notes can be found at https://aka.ms/MicrosoftFeatureManagementReleaseNotes + Microsoft FeatureManagement FeatureFlags ApplicationInsights + https://aka.ms/AzureAppConfigurationPackageIcon + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\XMLComments\$(MSBuildProjectName).xml + + + + + + + diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs new file mode 100644 index 00000000..5614a3f3 --- /dev/null +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +using Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore +{ + /// + /// Used to add targeting information to outgoing Application Insights telemetry. + /// + public class TargetingTelemetryInitializer : TelemetryInitializerBase + { + private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId"; + + /// + /// Creates an instance of the TargetingTelemetryInitializer + /// + public TargetingTelemetryInitializer(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + { + } + + /// + /// When telemetry is initialized, adds targeting information to all relevant telemetry. + /// + /// The to get the targeting information from. + /// The relevant to the telemetry. + /// The to be initialized. + /// Thrown if the any param is null. + protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTelemetry requestTelemetry, ITelemetry telemetry) + { + if (telemetry == null) + { + throw new ArgumentNullException("telemetry"); + } + + if (httpContext == null) + { + throw new ArgumentNullException("httpContext"); + } + + // Extract the targeting id from the http context + string targetingId = null; + + if (httpContext.Items.TryGetValue(TargetingIdKey, out object value)) + { + targetingId = value?.ToString(); + } + + if (!string.IsNullOrEmpty(targetingId)) + { + // Telemetry.Properties is deprecated in favor of ISupportProperties + if (telemetry is ISupportProperties telemetryWithSupportProperties) + { + telemetryWithSupportProperties.Properties["TargetingId"] = targetingId; + } + } + } + } +}