Skip to content

Commit 1a06766

Browse files
Update Variant Example Application (#437)
* add example for variant based injection * readme added * not use third-party api * update * update README * fix typo * use latest api
1 parent 948cb4a commit 1a06766

File tree

86 files changed

+75201
-29
lines changed

Some content is hidden

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

86 files changed

+75201
-29
lines changed

Microsoft.FeatureManagement.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvaluationDataToApplication
2929
EndProject
3030
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}"
3131
EndProject
32-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServerApp", "examples\BlazorServerApp\BlazorServerApp.csproj", "{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}"
32+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServerApp", "examples\BlazorServerApp\BlazorServerApp.csproj", "{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}"
33+
EndProject
34+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VariantServiceDemo", "examples\VariantServiceDemo\VariantServiceDemo.csproj", "{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F}"
3335
EndProject
3436
Global
3537
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -85,6 +87,10 @@ Global
8587
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Debug|Any CPU.Build.0 = Debug|Any CPU
8688
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.ActiveCfg = Release|Any CPU
8789
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.Build.0 = Release|Any CPU
90+
{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91+
{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
92+
{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
93+
{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F}.Release|Any CPU.Build.0 = Release|Any CPU
8894
EndGlobalSection
8995
GlobalSection(SolutionProperties) = preSolution
9096
HideSolutionNode = FALSE
@@ -98,6 +104,7 @@ Global
98104
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
99105
{1502529E-47E9-4306-98C4-BF6CF7C7C275} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
100106
{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
107+
{E8E17CB9-434E-4386-BF96-FA53BBFDCD6F} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
101108
EndGlobalSection
102109
GlobalSection(ExtensibilityGlobals) = postSolution
103110
SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Feature management provides a way to develop and expose application functionalit
1818
* [ASP.NET Core Web App (MVC)](./examples/FeatureFlagDemo)
1919
* [Blazor Server App](./examples/BlazorServerApp)
2020
* [ASP.NET Core Web App with Feature Flag Telemetry](./examples/EvaluationDataToApplicationInsights)
21+
* [ASP.NET Core Web App with Variant Service](./examples/VariantServiceDemo)
2122

2223
## Contributing
2324

examples/BlazorServerApp/README.md

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,44 @@ This app demonstrates how to use the Feature Management library in Blazor apps.
1717
This app uses two feature flags: "BrowserEnhancement" and "Beta".
1818

1919
``` json
20-
"FeatureManagement": {
21-
"BrowserEnhancement": {
22-
"EnabledFor": [
23-
{
24-
"Name": "Browser",
25-
"Parameters": {
26-
"AllowedBrowsers": [ "Edge" ]
27-
}
20+
"feature_management": {
21+
"feature_flags": [
22+
{
23+
"id": "BrowserEnhancement",
24+
"enabled": true,
25+
"conditions": {
26+
"client_filters": [
27+
{
28+
"name": "Browser",
29+
"parameters": {
30+
"AllowedBrowsers": [ "Edge" ]
2831
}
32+
}
2933
]
34+
}
3035
},
31-
"Beta": {
32-
"EnabledFor": [
33-
{
34-
"Name": "Targeting",
35-
"Parameters": {
36-
"Audience": {
37-
"DefaultRolloutPercentage": 50,
38-
"Exclusion": {
39-
"Groups": [
40-
"Guests"
41-
]
42-
}
43-
}
36+
{
37+
"id": "Beta",
38+
"enabled": true,
39+
"conditions": {
40+
"client_filters": [
41+
{
42+
"name": "Targeting",
43+
"parameters": {
44+
"Audience": {
45+
"DefaultRolloutPercentage": 50,
46+
"Exclusion": {
47+
"Groups": [
48+
"Guests"
49+
]
4450
}
51+
}
4552
}
53+
}
4654
]
55+
}
4756
}
57+
]
4858
}
4959
```
5060

@@ -65,6 +75,7 @@ This app uses [cookie authentication](https://learn.microsoft.com/en-us/aspnet/c
6575
Rather than `HttpContext`, the [`AuthenticationStateProvider`](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-8.0#authenticationstateprovider-service) service is used to obtain the user authentication state information for setting targeting context. The details can be found in the [`MyTargetingContextAccessor`](./MyTargetingContextAccessor.cs).
6676

6777
## Service Registration
78+
6879
Blazor applications like this one typically pull ambient contextual data from scoped services. For example, the `UserAgentContext`, `AuthenticationStateProvider` and `ITargetingContextAccessor` are all scoped services. This pattern *breaks* if the feature management services are added as singleton, which is typical in non-blazor web apps.
6980

7081
In Blazor, *avoid* the following

examples/EvaluationDataToApplicationInsights/Pages/Shared/_Layout.cshtml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
@using System.Security.Claims
22
@using Microsoft.AspNetCore.Http
33

4-
@inject IHttpContextAccessor httpContextAccessor
5-
@{
6-
string username = httpContextAccessor.HttpContext.Request.Cookies["username"];
7-
}
8-
94
<!DOCTYPE html>
105
<html lang="en">
116
<head>

examples/EvaluationDataToApplicationInsights/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33
//
4-
using Microsoft.FeatureManagement;
54
using EvaluationDataToApplicationInsights;
65
using Microsoft.ApplicationInsights.Extensibility;
6+
using Microsoft.FeatureManagement;
77
using Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore;
88

99
var builder = WebApplication.CreateBuilder(args);
@@ -55,7 +55,7 @@
5555
app.MapRazorPages();
5656

5757
//
58-
// Adds Targeting Id to HttpContext
58+
// Add Targeting Id to HttpContext
5959
app.UseMiddleware<TargetingHttpContextMiddleware>();
6060

6161
app.Run();

examples/EvaluationDataToApplicationInsights/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ These logs show what would be emitted to a connected Application Insights resour
2424
To flow these events to Application Insights, [setup a new Application Insights resource in Azure](https://learn.microsoft.com/en-us/azure/azure-monitor/app/create-workspace-resource). Once setup, from `Overview` copy the `Connection String` and place it in `appsettings.json` at ApplicationInsights > ConnectionString. After restarting the app, events should now flow to Application Insights. This [document](https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew%2Cnetcore6#enable-application-insights-server-side-telemetry-no-visual-studio) provides more details on connecting a .NET application to Application Insights.
2525

2626
## About the App
27+
2728
This app uses [Application Insights for ASP.NET Core](https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew%2Cnetcore6). This means there is an App Insights SDK in the C# code and a separate App Insights SDK the Javascript. Lets cover what they're doing:
2829

2930
### Javascript App Insights SDK
31+
3032
See [Enable cliend-side telemetry for web applications](https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew%2Cnetcore6#enable-client-side-telemetry-for-web-applications)
3133

3234
For ASP.NET, this is added to _ViewImports.cshtml
@@ -47,11 +49,13 @@ These cookies are used to correlate telemetry from the browser with telemetry fr
4749
*The Javascript SDK is not required, but is useful for collecting browser telemetry and generating these cookies out of the box.*
4850

4951
### Targeting Id
52+
5053
In order to connect evaluation events with other metrics from the user, a targeting id needs to be emitted. This can be done multiple ways, but the recommended way is to define a telemetry initializer. This initializer allows the app to modify all telemetry going to Application Insights before it's sent.
5154

5255
This example uses the provided `TargetingHttpContextMiddleware` and `TargetingTelemetryInitializer`. The middleware adds `TargetingId` (using the targeting context accessor) to the HTTP Context as a request comes in. The initializer checks for the `TargetingId` on the HTTP Context, and if it exists, adds `TargetingId` to all outgoing Application Insights Telemetry.
5356

5457
## Sample App Usage
58+
5559
Sample steps to try out the app:
5660

5761
1. Run the app. When the app is first started a User Id and Session Id will be generated. The username cookie will be set to a random integer, and the ai_user and ai_session cookies will be expired.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.FeatureManagement;
5+
6+
namespace VariantServiceDemo
7+
{
8+
[VariantServiceAlias("DefaultCalculator")]
9+
public class DefaultCalculator : ICalculator
10+
{
11+
public Task<double> AddAsync(double a, double b)
12+
{
13+
return Task.FromResult(a + b);
14+
}
15+
}
16+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Microsoft.FeatureManagement.FeatureFilters;
5+
6+
namespace VariantServiceDemo
7+
{
8+
/// <summary>
9+
/// Provides an implementation of <see cref="ITargetingContextAccessor"/> that creates a targeting context using info from the current HTTP request.
10+
/// </summary>
11+
public class HttpContextTargetingContextAccessor : ITargetingContextAccessor
12+
{
13+
private const string TargetingContextLookup = "HttpContextTargetingContextAccessor.TargetingContext";
14+
private readonly IHttpContextAccessor _httpContextAccessor;
15+
16+
public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
17+
{
18+
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
19+
}
20+
21+
public ValueTask<TargetingContext> GetContextAsync()
22+
{
23+
HttpContext httpContext = _httpContextAccessor.HttpContext;
24+
25+
//
26+
// Try cache lookup
27+
if (httpContext.Items.TryGetValue(TargetingContextLookup, out object value))
28+
{
29+
return new ValueTask<TargetingContext>((TargetingContext)value);
30+
}
31+
32+
//
33+
// Grab username from cookie
34+
string username = httpContext.Request.Cookies["username"];
35+
36+
var groups = new List<string>();
37+
38+
//
39+
// Build targeting context based on user info
40+
var targetingContext = new TargetingContext
41+
{
42+
UserId = username,
43+
Groups = groups
44+
};
45+
46+
//
47+
// Cache for subsequent lookup
48+
httpContext.Items[TargetingContextLookup] = targetingContext;
49+
50+
return new ValueTask<TargetingContext>(targetingContext);
51+
}
52+
}
53+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
namespace VariantServiceDemo
5+
{
6+
public interface ICalculator
7+
{
8+
public Task<double> AddAsync(double a, double b);
9+
}
10+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@page
2+
@model ErrorModel
3+
@{
4+
ViewData["Title"] = "Error";
5+
}
6+
7+
<h1 class="text-danger">Error.</h1>
8+
<h2 class="text-danger">An error occurred while processing your request.</h2>
9+
10+
@if (Model.ShowRequestId)
11+
{
12+
<p>
13+
<strong>Request ID:</strong> <code>@Model.RequestId</code>
14+
</p>
15+
}
16+
17+
<h3>Development Mode</h3>
18+
<p>
19+
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
20+
</p>
21+
<p>
22+
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
23+
It can result in displaying sensitive information from exceptions to end users.
24+
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
25+
and restarting the app.
26+
</p>

0 commit comments

Comments
 (0)