Skip to content

Commit

Permalink
Merge pull request #295 from microsoft/zhiyuanliang/preview-merge-fro…
Browse files Browse the repository at this point in the history
…m-main

preview merge from main
  • Loading branch information
zhiyuanliang-ms authored Oct 31, 2023
2 parents e05591c + 51bcf73 commit 195325e
Show file tree
Hide file tree
Showing 174 changed files with 134,336 additions and 9,741 deletions.
52 changes: 26 additions & 26 deletions Microsoft.FeatureManagement.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31825.309
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeatureFlagDemo", "examples\FeatureFlagDemo\FeatureFlagDemo.csproj", "{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement", "src\Microsoft.FeatureManagement\Microsoft.FeatureManagement.csproj", "{ED1A3494-6D5B-4B27-BA9C-8BAF93E36955}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.FeatureManagement", "tests\Tests.FeatureManagement\Tests.FeatureManagement.csproj", "{FDBB27BA-C5BA-48A7-BA9B-63159943EA9F}"
Expand All @@ -15,24 +13,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "examples\ConsoleApp\ConsoleApp.csproj", "{E50FB931-7A42-440E-AC47-B8DFE5E15394}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.FeatureManagement.AspNetCore", "tests\Tests.FeatureManagement.AspNetCore\Tests.FeatureManagement.AspNetCore.csproj", "{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "examples\ConsoleApp\ConsoleApp.csproj", "{7B98D293-F270-423E-A9A6-0D388E903AE9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "examples\TargetingConsoleApp\TargetingConsoleApp.csproj", "{6558C21E-CF20-4278-AA08-EB9D1DF29D66}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeatureFlagDemo", "examples\FeatureFlagDemo\FeatureFlagDemo.csproj", "{DACAB624-4611-42E8-844C-529F93A54980}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FeatureManagement.AspNetCore", "tests\Tests.FeatureManagement.AspNetCore\Tests.FeatureManagement.AspNetCore.csproj", "{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TargetingConsoleApp", "examples\TargetingConsoleApp\TargetingConsoleApp.csproj", "{283D3EBB-4716-4F1D-BA51-A435F7E2AB82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F}.Release|Any CPU.Build.0 = Release|Any CPU
{ED1A3494-6D5B-4B27-BA9C-8BAF93E36955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED1A3494-6D5B-4B27-BA9C-8BAF93E36955}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED1A3494-6D5B-4B27-BA9C-8BAF93E36955}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -45,33 +41,37 @@ Global
{CFD3E549-2E24-490D-A7F6-F95E56A81092}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFD3E549-2E24-490D-A7F6-F95E56A81092}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFD3E549-2E24-490D-A7F6-F95E56A81092}.Release|Any CPU.Build.0 = Release|Any CPU
{E50FB931-7A42-440E-AC47-B8DFE5E15394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E50FB931-7A42-440E-AC47-B8DFE5E15394}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E50FB931-7A42-440E-AC47-B8DFE5E15394}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E50FB931-7A42-440E-AC47-B8DFE5E15394}.Release|Any CPU.Build.0 = Release|Any CPU
{6558C21E-CF20-4278-AA08-EB9D1DF29D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6558C21E-CF20-4278-AA08-EB9D1DF29D66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6558C21E-CF20-4278-AA08-EB9D1DF29D66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6558C21E-CF20-4278-AA08-EB9D1DF29D66}.Release|Any CPU.Build.0 = Release|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU
{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4}.Release|Any CPU.Build.0 = Release|Any CPU
{7B98D293-F270-423E-A9A6-0D388E903AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B98D293-F270-423E-A9A6-0D388E903AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B98D293-F270-423E-A9A6-0D388E903AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B98D293-F270-423E-A9A6-0D388E903AE9}.Release|Any CPU.Build.0 = Release|Any CPU
{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3}.Release|Any CPU.Build.0 = Release|Any CPU
{DACAB624-4611-42E8-844C-529F93A54980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DACAB624-4611-42E8-844C-529F93A54980}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DACAB624-4611-42E8-844C-529F93A54980}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DACAB624-4611-42E8-844C-529F93A54980}.Release|Any CPU.Build.0 = Release|Any CPU
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E58A64A6-BE10-4D7A-AAB8-C3E2925CB32F} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{FDBB27BA-C5BA-48A7-BA9B-63159943EA9F} = {8ED6FFEE-4037-49A2-9709-BC519C104A90}
{E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{FC0DC3E2-5646-4AEC-A7DB-2D6167BC3BB4} = {8ED6FFEE-4037-49A2-9709-BC519C104A90}
{7B98D293-F270-423E-A9A6-0D388E903AE9} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{36DBB413-D9CA-4C56-AE5B-EAEA4C344DB3} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{DACAB624-4611-42E8-844C-529F93A54980} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD}
Expand Down
48 changes: 20 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning

### Status


`Status` is an optional property of a feature flag that controls how a flag's enabled state is evaluated. By default, the status of a flag is `Conditional`, meaning that feature filters should be evaluated to determine if the flag is enabled. If the `Status` of a flag is set to `Disabled` then feature filters are not evaluated and the flag is always considered to be disabled.


Expand All @@ -158,21 +157,7 @@ In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning
```

In this example, even though the `AlwaysOn` filter would normally always make the feature enabled, the `Status` property is set to `Disabled`, so this feature will always be disabled.

### Referencing

To make it easier to reference these feature flags in code, we recommend to define feature flag variables like below.

``` C#
// Define feature flags in an enum
public enum MyFeatureFlags
{
FeatureT,
FeatureU,
FeatureV
}
```


### Service Registration

Feature flags rely on .NET Core dependency injection. We can register the feature management services using standard conventions.
Expand All @@ -194,7 +179,6 @@ public class Startup

This tells the feature manager to use the "FeatureManagement" section from the configuration for feature flag settings. It also registers two built-in feature filters named `PercentageFilter` and `TimeWindowFilter`. When filters are referenced in feature flag settings (appsettings.json) the _Filter_ part of the type name can be omitted.


**Advanced:** The feature manager looks for feature definitions in a configuration section named "FeatureManagement". If the "FeatureManagement" section does not exist, it falls back to the root of the provided configuration.

## Consumption
Expand All @@ -207,7 +191,7 @@ The basic form of feature management is checking if a feature is enabled and the
IFeatureManager featureManager;
if (await featureManager.IsEnabledAsync(nameof(MyFeatureFlags.FeatureU)))
if (await featureManager.IsEnabledAsync("FeatureX"))
{
// Do something
}
Expand Down Expand Up @@ -236,7 +220,7 @@ The feature management library provides functionality in ASP.NET Core and MVC to
MVC controller and actions can require that a given feature, or one of any list of features, be enabled in order to execute. This can be done by using a `FeatureGateAttribute`, which can be found in the `Microsoft.FeatureManagement.Mvc` namespace.

``` C#
[FeatureGate(MyFeatureFlags.FeatureX)]
[FeatureGate("FeatureX")]
public class HomeController : Controller
{
Expand All @@ -246,14 +230,14 @@ public class HomeController : Controller
The `HomeController` above is gated by "FeatureX". "FeatureX" must be enabled before any action the `HomeController` contains can be executed.

``` C#
[FeatureGate(MyFeatureFlags.FeatureY)]
[FeatureGate("FeatureX")]
public IActionResult Index()
{
return View();
}
```

The `Index` MVC action above requires "FeatureY" to be enabled before it can execute.
The `Index` MVC action above requires "FeatureX" to be enabled before it can execute.

### Disabled Action Handling

Expand All @@ -271,11 +255,19 @@ public interface IDisabledFeaturesHandler
In MVC views `<feature>` tags can be used to conditionally render content based on whether a feature is enabled or not.

``` HTML+Razor
<feature name=@nameof(MyFeatureFlags.FeatureX)>
<feature name="FeatureX">
<p>This can only be seen if 'FeatureX' is enabled.</p>
</feature>
```

You can also negate the tag helper evaluation to display content when a feature or set of features are disabled. By setting `negate="true"` in the example below, the content is only rendered if `FeatureX` is disabled.

``` HTML+Razor
<feature negate="true" name="FeatureX">
<p>This can only be seen if 'FeatureX' is disabled.</p>
</feature>
```

The `<feature>` tag requires a tag helper to work. This can be done by adding the feature management tag helper to the _ViewImports.cshtml_ file.
``` HTML+Razor
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore
Expand All @@ -289,17 +281,17 @@ The feature management pipeline supports async MVC Action filters, which impleme
``` C#
services.AddMvc(o =>
{
o.Filters.AddForFeature<SomeMvcFilter>(nameof(MyFeatureFlags.FeatureV));
o.Filters.AddForFeature<SomeMvcFilter>("FeatureX");
});
```

The code above adds an MVC filter named `SomeMvcFilter`. This filter is only triggered within the MVC pipeline if the feature it specifies, "FeatureV", is enabled.
The code above adds an MVC filter named `SomeMvcFilter`. This filter is only triggered within the MVC pipeline if the feature it specifies, "FeatureX", is enabled.

### Razor Pages
MVC Razor pages can require that a given feature, or one of any list of features, be enabled in order to execute. This can be done by using a `FeatureGateAttribute`, which can be found in the `Microsoft.FeatureManagement.Mvc` namespace.

``` C#
[FeatureGate(MyFeatureFlags.FeatureU)]
[FeatureGate("FeatureX")]
public class IndexModel : PageModel
{
public void OnGet()
Expand All @@ -308,7 +300,7 @@ public class IndexModel : PageModel
}
```

The code above sets up a Razor page to require the "FeatureU" to be enabled. If the feature is not enabled, the page will generate an HTTP 404 (NotFound) result.
The code above sets up a Razor page to require the "FeatureX" to be enabled. If the feature is not enabled, the page will generate an HTTP 404 (NotFound) result.

When used on Razor pages, the `FeatureGateAttribute` must be placed on the page handler type. It cannot be placed on individual handler methods.

Expand All @@ -317,10 +309,10 @@ When used on Razor pages, the `FeatureGateAttribute` must be placed on the page
The feature management library can be used to add application branches and middleware that execute conditionally based on feature state.

``` C#
app.UseMiddlewareForFeature<ThirdPartyMiddleware>(nameof(MyFeatureFlags.FeatureU));
app.UseMiddlewareForFeature<ThirdPartyMiddleware>("FeatureX");
```

With the above call, the application adds a middleware component that only appears in the request pipeline if the feature "FeatureU" is enabled. If the feature is enabled/disabled during runtime, the middleware pipeline can be changed dynamically.
With the above call, the application adds a middleware component that only appears in the request pipeline if the feature "FeatureX" is enabled. If the feature is enabled/disabled during runtime, the middleware pipeline can be changed dynamically.

This builds off the more generic capability to branch the entire application based on a feature.

Expand Down
11 changes: 3 additions & 8 deletions examples/ConsoleApp/AccountServiceContext.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Consoto.Banking.AccountService.FeatureFilters;

namespace Consoto.Banking.AccountService
class AccountServiceContext : IAccountContext
{
class AccountServiceContext : IAccountContext
{
public string AccountId { get; set; }
}
}
public string AccountId { get; set; }
}
5 changes: 3 additions & 2 deletions examples/ConsoleApp/ConsoleApp.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Consoto.Banking.AccountService</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
33 changes: 13 additions & 20 deletions examples/ConsoleApp/FeatureFilters/AccountIdFilter.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Consoto.Banking.AccountService.FeatureFilters;
using Microsoft.Extensions.Configuration;
using Microsoft.FeatureManagement;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Consoto.Banking.AccountService.FeatureManagement
/// <summary>
/// A filter that uses the feature management context to ensure that the current task has the notion of an account id, and that the account id is allowed.
/// This filter will only be executed if an object implementing <see cref="IAccountContext"/> is passed in during feature evaluation.
/// </summary>
[FilterAlias("AccountId")]
class AccountIdFilter : IContextualFeatureFilter<IAccountContext>
{
/// <summary>
/// A filter that uses the feature management context to ensure that the current task has the notion of an account id, and that the account id is allowed.
/// This filter will only be executed if an object implementing <see cref="IAccountContext"/> is passed in during feature evaluation.
/// </summary>
[FilterAlias("AccountId")]
class AccountIdFilter : IContextualFeatureFilter<IAccountContext>
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext)
{
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureEvaluationContext, IAccountContext accountContext)
if (string.IsNullOrEmpty(accountContext?.AccountId))
{
if (string.IsNullOrEmpty(accountContext?.AccountId))
{
throw new ArgumentNullException(nameof(accountContext));
}
throw new ArgumentNullException(nameof(accountContext));
}

var allowedAccounts = new List<string>();
var allowedAccounts = new List<string>();

featureEvaluationContext.Parameters.Bind("AllowedAccounts", allowedAccounts);
featureEvaluationContext.Parameters.Bind("AllowedAccounts", allowedAccounts);

return Task.FromResult(allowedAccounts.Contains(accountContext.AccountId));
}
return Task.FromResult(allowedAccounts.Contains(accountContext.AccountId));
}
}
7 changes: 2 additions & 5 deletions examples/ConsoleApp/FeatureFilters/IAccountContext.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
namespace Consoto.Banking.AccountService.FeatureFilters
public interface IAccountContext
{
public interface IAccountContext
{
string AccountId { get; }
}
string AccountId { get; }
}
Loading

0 comments on commit 195325e

Please sign in to comment.