Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 FeatureFlagDemo
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
{
/// <summary>
/// Provides a default implementation of <see cref="ITargetingContextAccessor"/> that creates <see cref="TargetingContext"/> using info from the current HTTP request.
/// </summary>
public class DefaultHttpTargetingContextAccessor : ITargetingContextAccessor
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
{
private const string TargetingContextLookup = "FeatureManagement.TargetingContext";
private readonly IHttpContextAccessor _httpContextAccessor;

/// <summary>
/// Creates an instance of the DefaultHttpTargetingContextAccessor
/// </summary>
public DefaultHttpTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}

/// <summary>
/// Gets <see cref="TargetingContext"/> from the current HTTP request.
/// </summary>
public ValueTask<TargetingContext> GetContextAsync()
{
HttpContext httpContext = _httpContextAccessor.HttpContext;

//
// Try cache lookup
if (httpContext.Items.TryGetValue(TargetingContextLookup, out object value))
{
return new ValueTask<TargetingContext>((TargetingContext)value);
}

//
// Treat user identity name as user id
ClaimsPrincipal user = httpContext.User;
string userId = user?.Identity?.Name;
Comment thread
rossgrambo marked this conversation as resolved.

//
// Treat claims of type Role as groups
IEnumerable<string> 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>(targetingContext);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class TargetingHttpContextMiddleware
private readonly RequestDelegate _next;
private readonly ILogger _logger;

private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId";
private const string TargetingContextLookup = "FeatureManagement.TargetingContext";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why we removed Microsoft prefix here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I can recall... I'll add it back.


/// <summary>
/// Creates an instance of the TargetingHttpContextMiddleware
Expand Down Expand Up @@ -48,9 +48,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(TargetingContextLookup))
{
context.Items[TargetingIdKey] = targetingContext.UserId;
context.Items[TargetingContextLookup] = targetingContext;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -14,7 +15,7 @@ namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore
/// </summary>
public class TargetingTelemetryInitializer : TelemetryInitializerBase
{
private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId";
private const string TargetingContextLookup = "FeatureManagement.TargetingContext";

/// <summary>
/// Creates an instance of the TargetingTelemetryInitializer
Expand Down Expand Up @@ -42,21 +43,15 @@ protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTe
throw new ArgumentNullException(nameof(httpContext));
}

// Extract the targeting id from the http context
string targetingId = null;
//
// Extract the targeting info from the http context
TargetingContext targetingContext = httpContext.Items[TargetingContextLookup] as TargetingContext;

if (httpContext.Items.TryGetValue(TargetingIdKey, out object value))
{
targetingId = value?.ToString();
}
string targetingId = targetingContext?.UserId ?? string.Empty;

if (!string.IsNullOrEmpty(targetingId))
if (telemetry is ISupportProperties telemetryWithSupportProperties)
{
// Telemetry.Properties is deprecated in favor of ISupportProperties
if (telemetry is ISupportProperties telemetryWithSupportProperties)
{
telemetryWithSupportProperties.Properties["TargetingId"] = targetingId;
}
telemetryWithSupportProperties.Properties["TargetingId"] = targetingId;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down