Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
57d2243
init
zhiyuanliang-ms Dec 19, 2023
f9506c9
draft
zhiyuanliang-ms Dec 19, 2023
f9c1484
Merge branch 'preview' into zhiyuanliang/feature-based-injection
zhiyuanliang-ms Dec 19, 2023
5edd9be
use ValueTask
zhiyuanliang-ms Dec 19, 2023
06f410b
Merge branch 'preview' of https://github.com/microsoft/FeatureManagem…
zhiyuanliang-ms Dec 20, 2023
e1b2450
support factory method
zhiyuanliang-ms Dec 25, 2023
f497405
add example
zhiyuanliang-ms Dec 25, 2023
2b7ad46
Merge branch 'preview' of https://github.com/microsoft/FeatureManagem…
zhiyuanliang-ms Jan 8, 2024
d89abc8
update
zhiyuanliang-ms Jan 9, 2024
ca772ec
Update
zhiyuanliang-ms Jan 9, 2024
3710dab
Merge branch 'preview' into zhiyuanliang/feature-based-injection
zhiyuanliang-ms Jan 9, 2024
b92fefb
Update
zhiyuanliang-ms Jan 9, 2024
f3926f8
update example
zhiyuanliang-ms Jan 9, 2024
0d0f3f3
match variant name or configuration value
zhiyuanliang-ms Jan 9, 2024
176cf78
update to the latest design
zhiyuanliang-ms Jan 10, 2024
a7b8f0f
merge with preview branch
zhiyuanliang-ms Jan 18, 2024
73aac10
resolve comments
zhiyuanliang-ms Jan 18, 2024
25f91e9
remove check for variant value
zhiyuanliang-ms Jan 19, 2024
3ed4c4c
rename to VariantService
zhiyuanliang-ms Jan 19, 2024
941c7a1
update & add comments
zhiyuanliang-ms Jan 19, 2024
18ae0eb
remove POC example
zhiyuanliang-ms Jan 19, 2024
f48767c
add testcases & use method name GetServiceAsync
zhiyuanliang-ms Jan 22, 2024
1556c0d
update comments
zhiyuanliang-ms Jan 22, 2024
7120abd
add variant service cache
zhiyuanliang-ms Jan 29, 2024
1a4e0fa
resolve comments
zhiyuanliang-ms Jan 31, 2024
926182a
throw exception for duplicated registration
zhiyuanliang-ms Feb 1, 2024
1065d76
add testcase
zhiyuanliang-ms Feb 1, 2024
37cda1b
remove unused package
zhiyuanliang-ms Feb 7, 2024
df47105
update comment
zhiyuanliang-ms Feb 7, 2024
cbda0c6
set feature name in constructor
zhiyuanliang-ms Feb 9, 2024
6488a81
Merge branch 'preview' into zhiyuanliang/feature-based-injection
zhiyuanliang-ms Feb 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Microsoft.FeatureManagement.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvaluationDataToApplicationInsights", "examples\EvaluationDataToApplicationInsights\EvaluationDataToApplicationInsights.csproj", "{1502529E-47E9-4306-98C4-BF6CF7C7C275}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeatureBasedInjectionPOC", "examples\FeatureBasedInjectionPOC\FeatureBasedInjectionPOC.csproj", "{6655016D-900B-42D2-AA43-C33B0E161D6B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -73,6 +75,10 @@ Global
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.Build.0 = Release|Any CPU
{6655016D-900B-42D2-AA43-C33B0E161D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6655016D-900B-42D2-AA43-C33B0E161D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6655016D-900B-42D2-AA43-C33B0E161D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6655016D-900B-42D2-AA43-C33B0E161D6B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -85,6 +91,7 @@ Global
{DACAB624-4611-42E8-844C-529F93A54980} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{283D3EBB-4716-4F1D-BA51-A435F7E2AB82} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{1502529E-47E9-4306-98C4-BF6CF7C7C275} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{6655016D-900B-42D2-AA43-C33B0E161D6B} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD}
Expand Down
12 changes: 12 additions & 0 deletions examples/FeatureBasedInjectionPOC/AlgorithmAlpha.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FeatureBasedInjectionPOC
{
internal class AlgorithmAlpha : IAlgorithm
{
public string Name { get; set; }

public AlgorithmAlpha()
{
Name = "Alpha";
}
}
}
12 changes: 12 additions & 0 deletions examples/FeatureBasedInjectionPOC/AlgorithmBeta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FeatureBasedInjectionPOC
{
internal class AlgorithmBeta : IAlgorithm
{
public string Name { get; set; }

public AlgorithmBeta()
{
Name = "Beta";
}
}
}
15 changes: 15 additions & 0 deletions examples/FeatureBasedInjectionPOC/AlgorithmOmega.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.FeatureManagement;

namespace FeatureBasedInjectionPOC
{
[FeaturedServiceAlias("Omega")]
class AlgorithmOmega : IAlgorithm
{
public string Name { get; set; }

public AlgorithmOmega(string name)
{
Name = name;
}
}
}
12 changes: 12 additions & 0 deletions examples/FeatureBasedInjectionPOC/AlgorithmSigma.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FeatureBasedInjectionPOC
{
internal class AlgorithmSigma : IAlgorithm
{
public string Name { get; set; }

public AlgorithmSigma()
{
Name = "Sigma";
}
}
}
25 changes: 25 additions & 0 deletions examples/FeatureBasedInjectionPOC/FeatureBasedInjectionPOC.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement\Microsoft.FeatureManagement.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions examples/FeatureBasedInjectionPOC/IAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace FeatureBasedInjectionPOC
{
public interface IAlgorithm
{
public string Name { get; }
Comment thread
zhiyuanliang-ms marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.FeatureManagement.FeatureFilters;

namespace FeatureBasedInjectionPOC
{
class OnDemandTargetingContextAccessor : ITargetingContextAccessor
{
public TargetingContext Current { get; set; }

public ValueTask<TargetingContext> GetContextAsync()
{
return new ValueTask<TargetingContext>(Current);
}
}
}
77 changes: 77 additions & 0 deletions examples/FeatureBasedInjectionPOC/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
using FeatureBasedInjectionPOC;


IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();

IServiceCollection services = new ServiceCollection();

services.AddSingleton<IAlgorithm, AlgorithmAlpha>();
services.AddSingleton<IAlgorithm, AlgorithmBeta>();
services.AddSingleton<IAlgorithm, AlgorithmSigma>();
services.AddSingleton<IAlgorithm>(sp => new AlgorithmOmega("Omega"));

services.AddSingleton(configuration)
.AddFeatureManagement()
.AddFeatureFilter<TargetingFilter>()
.AddFeaturedService<IAlgorithm>("MyFeature");

var targetingContextAccessor = new OnDemandTargetingContextAccessor();

services.AddSingleton<ITargetingContextAccessor>(targetingContextAccessor);

using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
IVariantFeatureManager featureManager = serviceProvider.GetRequiredService<IVariantFeatureManager>();

IFeaturedService<IAlgorithm> featuredAlgorithm = serviceProvider.GetRequiredService<IFeaturedService<IAlgorithm>>();

targetingContextAccessor.Current = new TargetingContext
{
UserId = "Guest"
};

IAlgorithm algorithm = await featuredAlgorithm.GetAsync(CancellationToken.None);

Variant variant = await featureManager.GetVariantAsync("MyFeature", CancellationToken.None);

Console.WriteLine($"Get algorithm {algorithm?.Name ?? "Null"} because the feature variant is {variant?.Name ?? "Null"}");

targetingContextAccessor.Current = new TargetingContext
{
UserId = "UserBeta"
};

algorithm = await featuredAlgorithm.GetAsync(CancellationToken.None);

variant = await featureManager.GetVariantAsync("MyFeature", CancellationToken.None);

Console.WriteLine($"Get algorithm {algorithm?.Name ?? "Null"} because the feature variant is {variant?.Name ?? "Null"}");

targetingContextAccessor.Current = new TargetingContext
{
UserId = "UserSigma"
};

algorithm = await featuredAlgorithm.GetAsync(CancellationToken.None);

variant = await featureManager.GetVariantAsync("MyFeature", CancellationToken.None);

Console.WriteLine($"Get algorithm {algorithm?.Name ?? "Null"} because the feature variant is {variant?.Name ?? "Null"}");

targetingContextAccessor.Current = new TargetingContext
{
UserId = "UserOmega"
};

algorithm = await featuredAlgorithm.GetAsync(CancellationToken.None);

variant = await featureManager.GetVariantAsync("MyFeature", CancellationToken.None);

Console.WriteLine($"Get algorithm {algorithm?.Name ?? "Null"} because the feature variant is {variant?.Name ?? "Null"}");
Comment thread
zhiyuanliang-ms marked this conversation as resolved.
Outdated
}
52 changes: 52 additions & 0 deletions examples/FeatureBasedInjectionPOC/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"FeatureManagement": {
"MyFeature": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": {
"Audience": {
"Users": [
"UserOmega", "UserSigma", "UserBeta"
]
}
}
}
],
"Variants": [
{
"Name": "AlgorithmBeta"
},
{
"Name": "Sigma",
"ConfigurationValue": "AlgorithmSigma"
},
{
"Name": "Omega"
}
],
"Allocation": {
"User": [
{
"Variant": "AlgorithmBeta",
"Users": [
"UserBeta"
]
},
{
"Variant": "Omega",
"Users": [
"UserOmega"
]
},
{
"Variant": "Sigma",
"Users": [
"UserSigma"
]
}
]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Comment thread
zhiyuanliang-ms marked this conversation as resolved.
Outdated

namespace Microsoft.FeatureManagement
{
Expand All @@ -16,6 +17,32 @@ namespace Microsoft.FeatureManagement
/// </summary>
public static class FeatureManagementBuilderExtensions
{
/// <summary>
/// Adds a <see cref="FeaturedService{TService}"/> to the feature management system.
/// </summary>
/// <param name="builder">The <see cref="IFeatureManagementBuilder"/> used to customize feature management functionality.</param>
/// <param name="featureName">The variant feature flag used to assign variants. The <see cref="FeaturedService{TService}"/> will return different implementations of TService according to the assigned variant.</param>
/// <returns>A <see cref="IFeatureManagementBuilder"/> that can be used to customize feature management functionality.</returns>
public static IFeatureManagementBuilder AddFeaturedService<TService>(this IFeatureManagementBuilder builder, string featureName) where TService : class
{
if (builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IFeatureManager) && descriptor.Lifetime == ServiceLifetime.Scoped))
{
builder.Services.AddScoped<IFeaturedService<TService>>(sp => new FeaturedService<TService>(
featureName,
sp.GetRequiredService<IEnumerable<TService>>(),
sp.GetRequiredService<IVariantFeatureManager>()));
}
else
{
builder.Services.AddSingleton<IFeaturedService<TService>>(sp => new FeaturedService<TService>(
featureName,
sp.GetRequiredService<IEnumerable<TService>>(),
sp.GetRequiredService<IVariantFeatureManager>()));
}

return builder;
}

/// <summary>
/// Adds a telemetry publisher to the feature management system.
/// </summary>
Expand Down
64 changes: 64 additions & 0 deletions src/Microsoft.FeatureManagement/FeaturedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.FeatureManagement
{
internal class FeaturedService<TService> : IFeaturedService<TService> where TService : class
{
private readonly string _featureName;
private readonly IVariantFeatureManager _featureManager;
private readonly IEnumerable<TService> _services;

public FeaturedService(string featureName, IEnumerable<TService> services, IVariantFeatureManager featureManager)
{
_featureName = featureName;
_services = services;
_featureManager = featureManager;
}

public async ValueTask<TService> GetAsync(CancellationToken cancellationToken)
{
Variant variant = await _featureManager.GetVariantAsync(_featureName, cancellationToken);

TService implementation = null;

if (variant != null)
{
implementation = _services.FirstOrDefault(service =>
IsMatchingVariant(
service.GetType(),
variant));
}

return implementation;
}

private bool IsMatchingVariant(Type implementationType, Variant variant)
{
Debug.Assert(variant != null);

string implementationName = ((FeaturedServiceAliasAttribute)Attribute.GetCustomAttribute(implementationType, typeof(FeaturedServiceAliasAttribute)))?.Alias;

if (implementationName == null)
{
implementationName = implementationType.Name;
}

string variantConfiguration = variant.Configuration?.Value;
Comment thread
zhiyuanliang-ms marked this conversation as resolved.
Outdated

if (variantConfiguration != null && string.Equals(implementationName, variantConfiguration, StringComparison.OrdinalIgnoreCase))
{
return true;
}

return string.Equals(implementationName, variant.Name, StringComparison.OrdinalIgnoreCase);
}
}
}
22 changes: 22 additions & 0 deletions src/Microsoft.FeatureManagement/FeaturedServiceAliasAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System;

namespace Microsoft.FeatureManagement
{
public class FeaturedServiceAliasAttribute : Attribute
{
public FeaturedServiceAliasAttribute(string alias)
{
if (string.IsNullOrEmpty(alias))
{
throw new ArgumentNullException(nameof(alias));
}

Alias = alias;
}

public string Alias { get; }
}
}
Loading