Skip to content

Commit 3b0c61b

Browse files
authored
Adds edge case for EvaluateAsync call that doesn't use context from FeatureManager (#246) (#247)
* Adds edge case for EvaluateAsync call that doesn't use context from FeatureManager
1 parent 1622bca commit 3b0c61b

File tree

8 files changed

+80
-6
lines changed

8 files changed

+80
-6
lines changed

src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PropertyGroup>
66
<MajorVersion>2</MajorVersion>
77
<MinorVersion>6</MinorVersion>
8-
<PatchVersion>0</PatchVersion>
8+
<PatchVersion>1</PatchVersion>
99
</PropertyGroup>
1010

1111
<Import Project="..\..\build\Versioning.props" />

src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public object BindParameters(IConfiguration filterParameters)
4343
/// <returns>True if the feature is enabled, false otherwise.</returns>
4444
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
4545
{
46-
PercentageFilterSettings settings = (PercentageFilterSettings)context.Settings;
46+
//
47+
// Check if prebound settings available, otherwise bind from parameters.
48+
PercentageFilterSettings settings = (PercentageFilterSettings)context.Settings ?? (PercentageFilterSettings)BindParameters(context.Parameters);
4749

4850
bool result = true;
4951

src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public object BindParameters(IConfiguration filterParameters)
4343
/// <returns>True if the feature is enabled, false otherwise.</returns>
4444
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
4545
{
46-
TimeWindowFilterSettings settings = (TimeWindowFilterSettings)context.Settings;
46+
//
47+
// Check if prebound settings available, otherwise bind from parameters.
48+
TimeWindowFilterSettings settings = (TimeWindowFilterSettings)context.Settings ?? (TimeWindowFilterSettings)BindParameters(context.Parameters);
4749

4850
DateTimeOffset now = DateTimeOffset.UtcNow;
4951

src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PropertyGroup>
66
<MajorVersion>2</MajorVersion>
77
<MinorVersion>6</MinorVersion>
8-
<PatchVersion>0</PatchVersion>
8+
<PatchVersion>1</PatchVersion>
99
</PropertyGroup>
1010

1111
<Import Project="..\..\build\Versioning.props" />

src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context, ITargeti
6666
throw new ArgumentNullException(nameof(targetingContext));
6767
}
6868

69-
TargetingFilterSettings settings = (TargetingFilterSettings)context.Settings;
69+
//
70+
// Check if prebound settings available, otherwise bind from parameters.
71+
TargetingFilterSettings settings = (TargetingFilterSettings)context.Settings ?? (TargetingFilterSettings)BindParameters(context.Parameters);
7072

7173
if (!TryValidateSettings(settings, out string paramName, out string message))
7274
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Options;
7+
using Microsoft.FeatureManagement;
8+
using Microsoft.FeatureManagement.FeatureFilters;
9+
using System;
10+
using System.Threading.Tasks;
11+
12+
namespace Tests.FeatureManagement
13+
{
14+
[FilterAlias(Alias)]
15+
class CustomTargetingFilter : IFeatureFilter
16+
{
17+
private const string Alias = "CustomTargetingFilter";
18+
private readonly ContextualTargetingFilter _contextualFilter;
19+
20+
public CustomTargetingFilter(IOptions<TargetingEvaluationOptions> options, ILoggerFactory loggerFactory)
21+
{
22+
_contextualFilter = new ContextualTargetingFilter(options, loggerFactory);
23+
}
24+
25+
public Func<FeatureFilterEvaluationContext, Task<bool>> Callback { get; set; }
26+
27+
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
28+
{
29+
return _contextualFilter.EvaluateAsync(context, new TargetingContext(){ UserId = "Jeff" });
30+
}
31+
}
32+
}

tests/Tests.FeatureManagement/FeatureManagement.cs

+23-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,29 @@ public async Task GatesFeatures()
171171
Assert.Equal(HttpStatusCode.NotFound, gateAllResponse.StatusCode);
172172
Assert.Equal(HttpStatusCode.NotFound, gateAnyResponse.StatusCode);
173173
}
174-
174+
175+
[Fact]
176+
public async Task CustomFilterContextualTargetingWithNullSetting()
177+
{
178+
IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
179+
180+
ServiceCollection services = new ServiceCollection();
181+
182+
var targetingContextAccessor = new OnDemandTargetingContextAccessor();
183+
services.AddSingleton<ITargetingContextAccessor>(targetingContextAccessor);
184+
185+
services
186+
.AddSingleton(config)
187+
.AddFeatureManagement()
188+
.AddFeatureFilter<CustomTargetingFilter>();
189+
190+
ServiceProvider provider = services.BuildServiceProvider();
191+
192+
IFeatureManager featureManager = provider.GetRequiredService<IFeatureManager>();
193+
194+
Assert.True(await featureManager.IsEnabledAsync("CustomFilterFeature"));
195+
}
196+
175197
[Fact]
176198
public async Task GatesRazorPageFeatures()
177199
{

tests/Tests.FeatureManagement/appsettings.json

+14
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@
7070
}
7171
]
7272
},
73+
"CustomFilterFeature": {
74+
"EnabledFor": [
75+
{
76+
"Name": "CustomTargetingFilter",
77+
"Parameters": {
78+
"Audience": {
79+
"Users": [
80+
"Jeff"
81+
]
82+
}
83+
}
84+
}
85+
]
86+
},
7387
"ConditionalFeature": {
7488
"EnabledFor": [
7589
{

0 commit comments

Comments
 (0)