Skip to content

Commit 044ada8

Browse files
Merge branch 'preview' into zhiyuanliang/feature-based-injection
2 parents 0f691c0 + 8906633 commit 044ada8

File tree

56 files changed

+2402
-88
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2402
-88
lines changed

Microsoft.FeatureManagement.sln

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement
2727
EndProject
2828
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvaluationDataToApplicationInsights", "examples\EvaluationDataToApplicationInsights\EvaluationDataToApplicationInsights.csproj", "{1502529E-47E9-4306-98C4-BF6CF7C7C275}"
2929
EndProject
30+
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}"
31+
EndProject
32+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServerApp", "examples\BlazorServerApp\BlazorServerApp.csproj", "{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}"
33+
EndProject
3034
Global
3135
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3236
Debug|Any CPU = Debug|Any CPU
@@ -73,6 +77,14 @@ Global
7377
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Debug|Any CPU.Build.0 = Debug|Any CPU
7478
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.ActiveCfg = Release|Any CPU
7579
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.Build.0 = Release|Any CPU
80+
{C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81+
{C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.Build.0 = Debug|Any CPU
82+
{C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.ActiveCfg = Release|Any CPU
83+
{C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.Build.0 = Release|Any CPU
84+
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
85+
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Debug|Any CPU.Build.0 = Debug|Any CPU
86+
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.ActiveCfg = Release|Any CPU
87+
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.Build.0 = Release|Any CPU
7688
EndGlobalSection
7789
GlobalSection(SolutionProperties) = preSolution
7890
HideSolutionNode = FALSE
@@ -85,6 +97,7 @@ Global
8597
{DACAB624-4611-42E8-844C-529F93A54980} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
8698
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
8799
{1502529E-47E9-4306-98C4-BF6CF7C7C275} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
100+
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
88101
EndGlobalSection
89102
GlobalSection(ExtensibilityGlobals) = postSolution
90103
SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ This strategy for rolling out a feature is built-in to the library through the i
627627

628628
An example web application that uses the targeting feature filter is available in the [FeatureFlagDemo](./examples/FeatureFlagDemo) example project.
629629

630-
To begin using the `TargetingFilter` in an application it must be added to the application's service collection just as any other feature filter. Unlike other built in filters, the `TargetingFilter` relies on another service to be added to the application's service collection. That service is an `ITargetingContextAccessor`.
630+
To begin using the `TargetingFilter` in an application it must be added to the application's service collection just as any other feature filter. Unlike other built-in filters, the `TargetingFilter` relies on another service to be added to the application's service collection. That service is an `ITargetingContextAccessor`.
631631

632632
The implementation type used for the `ITargetingContextAccessor` service must be implemented by the application that is using the targeting filter. Here is an example setting up feature management in a web application to use the `TargetingFilter` with an implementation of `ITargetingContextAccessor` called `HttpContextTargetingContextAccessor`.
633633

examples/BlazorServerApp/App.razor

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<CascadingAuthenticationState>
2+
<Router AppAssembly="@typeof(App).Assembly">
3+
<Found Context="routeData">
4+
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
5+
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
6+
</Found>
7+
<NotFound>
8+
<PageTitle>Not found</PageTitle>
9+
<LayoutView Layout="@typeof(MainLayout)">
10+
<p role="alert">Sorry, there's nothing at this address.</p>
11+
</LayoutView>
12+
</NotFound>
13+
</Router>
14+
</CascadingAuthenticationState>
15+
16+
@inject UserAgentContext UserAgentContext
17+
18+
@code {
19+
[Parameter]
20+
public string UserAgent { get; set; }
21+
22+
protected override Task OnInitializedAsync()
23+
{
24+
UserAgentContext.UserAgent = UserAgent;
25+
26+
return base.OnInitializedAsync();
27+
}
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement\Microsoft.FeatureManagement.csproj" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Microsoft.FeatureManagement;
2+
3+
namespace BlazorServerApp
4+
{
5+
[FilterAlias("Browser")]
6+
public class BrowserFilter : IFeatureFilter
7+
{
8+
private const string Chrome = "Chrome";
9+
private const string Edge = "Edge";
10+
private const string Firefox = "Firefox";
11+
12+
private readonly UserAgentContext _userAgentContextProvider;
13+
14+
public BrowserFilter(UserAgentContext userAgentContextProvider)
15+
{
16+
_userAgentContextProvider = userAgentContextProvider ?? throw new ArgumentNullException(nameof(userAgentContextProvider));
17+
}
18+
19+
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
20+
{
21+
BrowserFilterSettings settings = context.Parameters.Get<BrowserFilterSettings>() ?? new BrowserFilterSettings();
22+
23+
string userAgentContext = _userAgentContextProvider.UserAgent;
24+
25+
if (settings.AllowedBrowsers.Any(browser => browser.Equals(Chrome, StringComparison.OrdinalIgnoreCase)) && IsChromeBrowser(userAgentContext))
26+
{
27+
return Task.FromResult(true);
28+
}
29+
else if (settings.AllowedBrowsers.Any(browser => browser.Equals(Edge, StringComparison.OrdinalIgnoreCase)) && IsEdgeBrowser(userAgentContext))
30+
{
31+
return Task.FromResult(true);
32+
}
33+
else if (settings.AllowedBrowsers.Any(browser => browser.Equals(Firefox, StringComparison.OrdinalIgnoreCase)) && IsFirefoxBrowser(userAgentContext))
34+
{
35+
return Task.FromResult(true);
36+
}
37+
38+
return Task.FromResult(false);
39+
}
40+
41+
private static bool IsChromeBrowser(string userAgentContext)
42+
{
43+
if (userAgentContext == null)
44+
{
45+
return false;
46+
}
47+
48+
return userAgentContext.Contains("chrome", StringComparison.OrdinalIgnoreCase) &&
49+
!userAgentContext.Contains("edg", StringComparison.OrdinalIgnoreCase);
50+
}
51+
52+
private static bool IsEdgeBrowser(string userAgentContext)
53+
{
54+
if (userAgentContext == null)
55+
{
56+
return false;
57+
}
58+
59+
return userAgentContext.Contains("edg", StringComparison.OrdinalIgnoreCase);
60+
}
61+
62+
private static bool IsFirefoxBrowser(string userAgentContext)
63+
{
64+
if (userAgentContext == null)
65+
{
66+
return false;
67+
}
68+
69+
return userAgentContext.Contains("firefox", StringComparison.OrdinalIgnoreCase);
70+
}
71+
}
72+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace BlazorServerApp
2+
{
3+
public class BrowserFilterSettings
4+
{
5+
public IList<string> AllowedBrowsers { get; set; } = new List<string>();
6+
}
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace BlazorServerApp.Data
2+
{
3+
public class WeatherForecast
4+
{
5+
public DateTime Date { get; set; }
6+
7+
public int TemperatureC { get; set; }
8+
9+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
10+
11+
public string Summary { get; set; }
12+
}
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace BlazorServerApp.Data
2+
{
3+
public class WeatherForecastService
4+
{
5+
private static readonly string[] Summaries = new[]
6+
{
7+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
8+
};
9+
10+
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
11+
{
12+
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
13+
{
14+
Date = startDate.AddDays(index),
15+
TemperatureC = Random.Shared.Next(-20, 55),
16+
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
17+
}).ToArray());
18+
}
19+
}
20+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.AspNetCore.Components.Authorization;
2+
using Microsoft.FeatureManagement.FeatureFilters;
3+
4+
namespace BlazorServerApp
5+
{
6+
public class MyTargetingContextAccessor : ITargetingContextAccessor
7+
{
8+
private readonly AuthenticationStateProvider _authenticationStateProvider;
9+
10+
public MyTargetingContextAccessor(AuthenticationStateProvider authenticationStateProvider)
11+
{
12+
_authenticationStateProvider = authenticationStateProvider ?? throw new ArgumentNullException(nameof(authenticationStateProvider));
13+
}
14+
15+
public async ValueTask<TargetingContext> GetContextAsync()
16+
{
17+
AuthenticationState authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
18+
19+
string username = authState.User.Identity.Name;
20+
21+
bool isAuthenticated = authState.User.Identity.IsAuthenticated;
22+
23+
var groups = new List<string>();
24+
25+
if (!isAuthenticated)
26+
{
27+
groups.Add("Guests");
28+
}
29+
30+
var targetingContext = new TargetingContext
31+
{
32+
UserId = username,
33+
Groups = groups
34+
};
35+
36+
return targetingContext;
37+
}
38+
}
39+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@page "/counter"
2+
3+
<PageTitle>Counter</PageTitle>
4+
5+
<AuthorizeView>
6+
<Authorized>
7+
<h1>Counter</h1>
8+
9+
<p role="status">Current count: @currentCount</p>
10+
11+
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
12+
</Authorized>
13+
</AuthorizeView>
14+
15+
@code {
16+
private int currentCount = 0;
17+
18+
private void IncrementCount()
19+
{
20+
currentCount++;
21+
}
22+
}

0 commit comments

Comments
 (0)