From 6c598af451760a3e1147c1743c39cbc8a50093da Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Fri, 13 Feb 2026 13:41:01 +0800 Subject: [PATCH 1/3] fix bug that session manager will be skipped if feature definition is null --- src/Microsoft.FeatureManagement/FeatureManager.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 00ff95a3..1cded402 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -371,6 +371,20 @@ private async ValueTask EvaluateFeature(string featur FeatureEvaluationTelemetry.Publish(evaluationEvent, Logger); } } + else if (_sessionManagers != null) + { + foreach (ISessionManager sessionManager in _sessionManagers) + { + bool? readSessionResult = await sessionManager.GetAsync(feature).ConfigureAwait(false); + + if (readSessionResult.HasValue) + { + evaluationEvent.Enabled = readSessionResult.Value; + + break; + } + } + } return evaluationEvent; } From 33630302fddca6fa73718f80446863d80f259d67 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Fri, 13 Feb 2026 14:04:04 +0800 Subject: [PATCH 2/3] add test --- .../FeatureManagementTest.cs | 30 ++++++++++++++++++ .../TestSessionManager.cs | 31 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 tests/Tests.FeatureManagement/TestSessionManager.cs diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index 10636b84..1bcf93be 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -280,6 +280,36 @@ public async Task ThrowsForMissingFeatures() featureManager.IsEnabledAsync("NonExistentFeature")); } + [Fact] + public async Task SessionManagerQueriedWhenFeatureDefinitionIsNull() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + ISessionManager sessionManager = new TestSessionManager(); + + await sessionManager.SetAsync("UnexistedFeature", true); + + services + .AddSingleton(config) + .AddSingleton(sessionManager) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + // Feature doesn't exist in configuration, but should return true from session + Assert.True(await featureManager.IsEnabledAsync("UnexistedFeature")); + + // Set the feature to false in session + await sessionManager.SetAsync("UnexistedFeature", false); + + // Should return false from session + Assert.False(await featureManager.IsEnabledAsync("UnexistedFeature")); + } + [Fact] public async Task ThreadSafeSnapshot() { diff --git a/tests/Tests.FeatureManagement/TestSessionManager.cs b/tests/Tests.FeatureManagement/TestSessionManager.cs new file mode 100644 index 00000000..37e0f635 --- /dev/null +++ b/tests/Tests.FeatureManagement/TestSessionManager.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +using Microsoft.FeatureManagement; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace Tests.FeatureManagement +{ + class TestSessionManager : ISessionManager + { + private readonly ConcurrentDictionary _session = new ConcurrentDictionary(); + + public Task SetAsync(string featureName, bool enabled) + { + _session[featureName] = enabled; + + return Task.CompletedTask; + } + + public Task GetAsync(string featureName) + { + if (_session.TryGetValue(featureName, out bool enabled)) + { + return Task.FromResult(enabled); + } + + return Task.FromResult(null); + } + } +} From 11b9273e1b37ba1df9d705427d2956aa95179ae0 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Sat, 21 Feb 2026 09:23:50 +0800 Subject: [PATCH 3/3] always set evaluation result to session manager --- .../FeatureManager.cs | 16 ++++++------ .../FeatureManagementTest.cs | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs index 1cded402..3cb35c7a 100644 --- a/src/Microsoft.FeatureManagement/FeatureManager.cs +++ b/src/Microsoft.FeatureManagement/FeatureManager.cs @@ -355,14 +355,6 @@ private async ValueTask EvaluateFeature(string featur } } - if (_sessionManagers != null) - { - foreach (ISessionManager sessionManager in _sessionManagers) - { - await sessionManager.SetAsync(evaluationEvent.FeatureDefinition.Name, evaluationEvent.Enabled).ConfigureAwait(false); - } - } - // Only add an activity event if telemetry is enabled for the feature and the activity is valid if (telemetryEnabled && Activity.Current != null && @@ -386,6 +378,14 @@ private async ValueTask EvaluateFeature(string featur } } + if (_sessionManagers != null) + { + foreach (ISessionManager sessionManager in _sessionManagers) + { + await sessionManager.SetAsync(feature, evaluationEvent.Enabled).ConfigureAwait(false); + } + } + return evaluationEvent; } diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs index 1bcf93be..12178672 100644 --- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs +++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs @@ -310,6 +310,31 @@ public async Task SessionManagerQueriedWhenFeatureDefinitionIsNull() Assert.False(await featureManager.IsEnabledAsync("UnexistedFeature")); } + [Fact] + public async Task SetResultInSessionManagerWhenFeatureDefinitionIsNull() + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + var services = new ServiceCollection(); + + ISessionManager sessionManager = new TestSessionManager(); + + services + .AddSingleton(config) + .AddSingleton(sessionManager) + .AddFeatureManagement(); + + ServiceProvider serviceProvider = services.BuildServiceProvider(); + + IFeatureManager featureManager = serviceProvider.GetRequiredService(); + + // session manager is clean here + var result = await featureManager.IsEnabledAsync("UnexistedFeature"); + + // session manager should store the result after evaluation + Assert.Equal(result, await sessionManager.GetAsync("UnexistedFeature")); + } + [Fact] public async Task ThreadSafeSnapshot() {