diff --git a/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs b/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs
new file mode 100644
index 00000000..c6fed688
--- /dev/null
+++ b/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+using Microsoft.AspNetCore.Http;
+using Microsoft.FeatureManagement.FeatureFilters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.FeatureManagement
+{
+ ///
+ /// Provides a default implementation of that creates using info from the current HTTP request.
+ ///
+ public sealed class DefaultHttpTargetingContextAccessor : ITargetingContextAccessor
+ {
+ ///
+ /// The key used to store and retrieve the from the items.
+ ///
+ public const string TargetingContextLookup = $"Microsoft.FeatureManagement.TargetingContext";
+
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Creates an instance of the DefaultHttpTargetingContextAccessor
+ ///
+ public DefaultHttpTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
+ {
+ _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
+ }
+
+ ///
+ /// Gets from the current HTTP request.
+ ///
+ public ValueTask GetContextAsync()
+ {
+ HttpContext httpContext = _httpContextAccessor.HttpContext;
+
+ //
+ // Try cache lookup
+ if (httpContext.Items.TryGetValue(TargetingContextLookup, out object value))
+ {
+ return new ValueTask((TargetingContext)value);
+ }
+
+ //
+ // Treat user identity name as user id
+ ClaimsPrincipal user = httpContext.User;
+
+ string userId = user?.Identity?.Name;
+
+ //
+ // Treat claims of type Role as groups
+ IEnumerable groups = httpContext.User.Claims
+ .Where(c => c.Type == ClaimTypes.Role)
+ .Select(c => c.Value);
+
+ TargetingContext targetingContext = new TargetingContext
+ {
+ UserId = userId,
+ Groups = groups
+ };
+
+ //
+ // Cache for subsequent lookup
+ httpContext.Items[TargetingContextLookup] = targetingContext;
+
+ return new ValueTask(targetingContext);
+ }
+ }
+}
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FeatureManagementBuilderExtensions.cs b/src/Microsoft.FeatureManagement.AspNetCore/FeatureManagementBuilderExtensions.cs
new file mode 100644
index 00000000..b0857451
--- /dev/null
+++ b/src/Microsoft.FeatureManagement.AspNetCore/FeatureManagementBuilderExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.FeatureManagement.FeatureFilters;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.FeatureManagement
+{
+ ///
+ /// Extensions used to add feature management functionality.
+ ///
+ public static class FeatureManagementBuilderExtensions
+ {
+ ///
+ /// Adds the to be used for targeting and registers the targeting filter to the feature management system.
+ ///
+ /// The used to customize feature management functionality.
+ /// A that can be used to customize feature management functionality.
+ public static IFeatureManagementBuilder WithTargeting(this IFeatureManagementBuilder builder)
+ {
+ //
+ // Register the targeting context accessor with the same lifetime as the feature manager
+ if (builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IFeatureManager) && descriptor.Lifetime == ServiceLifetime.Scoped))
+ {
+ builder.Services.TryAddScoped();
+ }
+ else
+ {
+ builder.Services.TryAddSingleton();
+ }
+
+ builder.AddFeatureFilter();
+
+ return builder;
+ }
+ }
+}
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs
index 8dd2378f..22166fe5 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs
@@ -18,8 +18,6 @@ public class TargetingHttpContextMiddleware
private readonly RequestDelegate _next;
private readonly ILogger _logger;
- private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId";
-
///
/// Creates an instance of the TargetingHttpContextMiddleware
///
@@ -48,9 +46,9 @@ public async Task InvokeAsync(HttpContext context, ITargetingContextAccessor tar
TargetingContext targetingContext = await targetingContextAccessor.GetContextAsync().ConfigureAwait(false);
- if (targetingContext != null)
+ if (targetingContext != null && !context.Items.ContainsKey(DefaultHttpTargetingContextAccessor.TargetingContextLookup))
{
- context.Items[TargetingIdKey] = targetingContext.UserId;
+ context.Items[DefaultHttpTargetingContextAccessor.TargetingContextLookup] = targetingContext;
}
else
{
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
index 0c4eb847..ae494bf2 100644
--- 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
@@ -37,7 +37,7 @@
-
+
diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs
index e01bd9e4..4f888d6f 100644
--- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs
+++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs
@@ -6,6 +6,7 @@
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.AspNetCore.Http;
+using Microsoft.FeatureManagement.FeatureFilters;
namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore
{
@@ -14,8 +15,6 @@ namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore
///
public class TargetingTelemetryInitializer : TelemetryInitializerBase
{
- private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId";
-
///
/// Creates an instance of the TargetingTelemetryInitializer
///
@@ -37,27 +36,24 @@ protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTe
throw new ArgumentNullException(nameof(telemetry));
}
+ if (telemetry is not ISupportProperties telemetryWithSupportProperties)
+ {
+ return;
+ }
+
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
- // Extract the targeting id from the http context
- string targetingId = null;
+ //
+ // Extract the targeting info from the http context
+ httpContext.Items.TryGetValue(DefaultHttpTargetingContextAccessor.TargetingContextLookup, out object targetingContextObject);
+ TargetingContext targetingContext = targetingContextObject as TargetingContext;
- if (httpContext.Items.TryGetValue(TargetingIdKey, out object value))
- {
- targetingId = value?.ToString();
- }
+ string targetingId = targetingContext?.UserId ?? string.Empty;
- if (!string.IsNullOrEmpty(targetingId))
- {
- // Telemetry.Properties is deprecated in favor of ISupportProperties
- if (telemetry is ISupportProperties telemetryWithSupportProperties)
- {
- telemetryWithSupportProperties.Properties["TargetingId"] = targetingId;
- }
- }
+ telemetryWithSupportProperties.Properties["TargetingId"] = targetingId;
}
}
}
diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs
index 20b05299..ca201c12 100644
--- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs
+++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/ApplicationInsightsTelemetryPublisher.cs
@@ -44,7 +44,7 @@ public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken
if (evaluationEvent.TargetingContext != null)
{
- properties["TargetingId"] = evaluationEvent.TargetingContext.UserId;
+ properties["TargetingId"] = evaluationEvent.TargetingContext.UserId ?? string.Empty;
}
if (evaluationEvent.VariantAssignmentReason != VariantAssignmentReason.None)