diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..233e6128
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,39 @@
+# editorconfig.org
+
+# top-most EditorConfig file
+root = true
+
+## Default settings ##
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+## Formatting rule ##
+# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055
+dotnet_diagnostic.IDE0055.severity = error
+
+# 'Using' directive preferences
+dotnet_sort_system_directives_first = false
+
+# New line preferences
+dotnet_diagnostic.IDE2002.severity = error
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
+dotnet_diagnostic.IDE2004.severity = error
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
+dotnet_diagnostic.IDE2005.severity = error
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
+dotnet_diagnostic.IDE2006.severity = error
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
+dotnet_diagnostic.IDE2000.severity = error
+dotnet_style_allow_multiple_blank_lines_experimental = false
+dotnet_diagnostic.IDE2003.severity = error
+dotnet_style_allow_statement_immediately_after_block_experimental = false
+
+[*.csproj]
+indent_size = 2
+charset = utf-8
+
+[*.json]
+indent_size = 2
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..e1220a8d
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+
+ True
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 2c46dbe4..5c9718c4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
# .NET Feature Management
+[](https://www.nuget.org/packages/Microsoft.FeatureManagement)
+[](https://www.nuget.org/packages/Microsoft.FeatureManagement.AspNetCore)
+
Feature management provides a way to develop and expose application functionality based on features. Many applications have special requirements when a new feature is developed such as when the feature should be enabled and under what conditions. This library provides a way to define these relationships, and also integrates into common .NET code patterns to make exposing these features possible.
## Get started
diff --git a/examples/BlazorServerApp/BlazorServerApp.csproj b/examples/BlazorServerApp/BlazorServerApp.csproj
index eeae537e..e1ab1c73 100644
--- a/examples/BlazorServerApp/BlazorServerApp.csproj
+++ b/examples/BlazorServerApp/BlazorServerApp.csproj
@@ -4,7 +4,7 @@
net6.0
enable
-
+
diff --git a/examples/BlazorServerApp/BrowserFilter.cs b/examples/BlazorServerApp/BrowserFilter.cs
index 5105b8a8..376f323e 100644
--- a/examples/BlazorServerApp/BrowserFilter.cs
+++ b/examples/BlazorServerApp/BrowserFilter.cs
@@ -45,7 +45,7 @@ private static bool IsChromeBrowser(string userAgentContext)
return false;
}
- return userAgentContext.Contains("chrome", StringComparison.OrdinalIgnoreCase) &&
+ return userAgentContext.Contains("chrome", StringComparison.OrdinalIgnoreCase) &&
!userAgentContext.Contains("edg", StringComparison.OrdinalIgnoreCase);
}
diff --git a/examples/BlazorServerApp/Program.cs b/examples/BlazorServerApp/Program.cs
index 4a66f68c..1dfd1c43 100644
--- a/examples/BlazorServerApp/Program.cs
+++ b/examples/BlazorServerApp/Program.cs
@@ -48,4 +48,4 @@ public static void Main(string[] args)
app.Run();
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/ConsoleApp/AccountServiceContext.cs b/examples/ConsoleApp/AccountServiceContext.cs
index 85114371..95f85a33 100644
--- a/examples/ConsoleApp/AccountServiceContext.cs
+++ b/examples/ConsoleApp/AccountServiceContext.cs
@@ -4,4 +4,4 @@
class AccountServiceContext : IAccountContext
{
public string AccountId { get; set; }
-}
\ No newline at end of file
+}
diff --git a/examples/ConsoleApp/Program.cs b/examples/ConsoleApp/Program.cs
index 12028ca2..fb43a50e 100644
--- a/examples/ConsoleApp/Program.cs
+++ b/examples/ConsoleApp/Program.cs
@@ -1,4 +1,7 @@
-using Microsoft.Extensions.Configuration;
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
@@ -49,4 +52,4 @@
// Output results
Console.WriteLine($"The {FeatureName} feature is {(enabled ? "enabled" : "disabled")} for the '{account}' account.");
}
-}
\ No newline at end of file
+}
diff --git a/examples/EvaluationDataToApplicationInsights/Pages/Checkout.cshtml.cs b/examples/EvaluationDataToApplicationInsights/Pages/Checkout.cshtml.cs
index eb5a8d00..ceaaef99 100644
--- a/examples/EvaluationDataToApplicationInsights/Pages/Checkout.cshtml.cs
+++ b/examples/EvaluationDataToApplicationInsights/Pages/Checkout.cshtml.cs
@@ -5,4 +5,4 @@ namespace EvaluationDataToApplicationInsights.Pages
public class CheckoutModel : PageModel
{
}
-}
\ No newline at end of file
+}
diff --git a/examples/EvaluationDataToApplicationInsights/Pages/Error.cshtml.cs b/examples/EvaluationDataToApplicationInsights/Pages/Error.cshtml.cs
index f34a941b..f20de344 100644
--- a/examples/EvaluationDataToApplicationInsights/Pages/Error.cshtml.cs
+++ b/examples/EvaluationDataToApplicationInsights/Pages/Error.cshtml.cs
@@ -24,4 +24,4 @@ public void OnGet()
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/EvaluationDataToApplicationInsights/Pages/Index.cshtml.cs b/examples/EvaluationDataToApplicationInsights/Pages/Index.cshtml.cs
index d161697e..0fbcf0b7 100644
--- a/examples/EvaluationDataToApplicationInsights/Pages/Index.cshtml.cs
+++ b/examples/EvaluationDataToApplicationInsights/Pages/Index.cshtml.cs
@@ -47,7 +47,7 @@ public IActionResult OnPost()
{
string val = Request.Form["imageScore"];
- if (val != null &&
+ if (val != null &&
int.TryParse(val, out int rating))
{
_telemetry.TrackEvent(
diff --git a/examples/FeatureFlagDemo/Authentication/QueryStringAuthenticationHandler.cs b/examples/FeatureFlagDemo/Authentication/QueryStringAuthenticationHandler.cs
index 073b7452..7770ed3d 100644
--- a/examples/FeatureFlagDemo/Authentication/QueryStringAuthenticationHandler.cs
+++ b/examples/FeatureFlagDemo/Authentication/QueryStringAuthenticationHandler.cs
@@ -2,14 +2,10 @@
// Licensed under the MIT license.
//
using Microsoft.AspNetCore.Authentication;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
-using System.Collections.Generic;
-using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
-using System.Threading.Tasks;
namespace FeatureFlagDemo.Authentication
{
@@ -49,7 +45,7 @@ protected override Task HandleAuthenticateAsync()
foreach (string group in groups)
{
- identity.AddClaim(new Claim(ClaimTypes.GroupName, group));
+ identity.AddClaim(new Claim(ClaimTypes.Role, group));
}
Logger.LogInformation($"Assigning the following groups '{string.Join(", ", groups)}' to the request.");
diff --git a/examples/FeatureFlagDemo/BrowserFilter.cs b/examples/FeatureFlagDemo/BrowserFilter.cs
index efeb8e70..335e6d4c 100644
--- a/examples/FeatureFlagDemo/BrowserFilter.cs
+++ b/examples/FeatureFlagDemo/BrowserFilter.cs
@@ -1,12 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
using Microsoft.FeatureManagement;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
namespace FeatureFlagDemo.FeatureManagement.FeatureFilters
{
diff --git a/examples/FeatureFlagDemo/BrowserFilterSettings.cs b/examples/FeatureFlagDemo/BrowserFilterSettings.cs
index 91b8211a..c4cce8a3 100644
--- a/examples/FeatureFlagDemo/BrowserFilterSettings.cs
+++ b/examples/FeatureFlagDemo/BrowserFilterSettings.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Collections.Generic;
-
namespace FeatureFlagDemo.FeatureManagement.FeatureFilters
{
public class BrowserFilterSettings
diff --git a/examples/FeatureFlagDemo/ClaimTypes.cs b/examples/FeatureFlagDemo/ClaimTypes.cs
deleted file mode 100644
index b24dfd20..00000000
--- a/examples/FeatureFlagDemo/ClaimTypes.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-//
-namespace FeatureFlagDemo
-{
- static class ClaimTypes
- {
- public static string GroupName = "http://schemas.featureflagdemo.featuremanagement.microsoft.com/claims/groupname";
- }
-}
diff --git a/examples/FeatureFlagDemo/Controllers/BetaController.cs b/examples/FeatureFlagDemo/Controllers/BetaController.cs
index 9b69d9cf..4fed13f8 100644
--- a/examples/FeatureFlagDemo/Controllers/BetaController.cs
+++ b/examples/FeatureFlagDemo/Controllers/BetaController.cs
@@ -7,7 +7,7 @@
namespace FeatureFlagDemo.Controllers
{
- public class BetaController: Controller
+ public class BetaController : Controller
{
private readonly IFeatureManager _featureManager;
diff --git a/examples/FeatureFlagDemo/Controllers/HomeController.cs b/examples/FeatureFlagDemo/Controllers/HomeController.cs
index 6e43fba2..e7bfcd43 100644
--- a/examples/FeatureFlagDemo/Controllers/HomeController.cs
+++ b/examples/FeatureFlagDemo/Controllers/HomeController.cs
@@ -1,13 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Diagnostics;
-using Microsoft.AspNetCore.Mvc;
using FeatureFlagDemo.Models;
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.Mvc;
-using System.Threading.Tasks;
+using System.Diagnostics;
namespace FeatureFlagDemo.Controllers
{
diff --git a/examples/FeatureFlagDemo/FeatureFlagDemo.csproj b/examples/FeatureFlagDemo/FeatureFlagDemo.csproj
index fcf18a50..94a10cc8 100644
--- a/examples/FeatureFlagDemo/FeatureFlagDemo.csproj
+++ b/examples/FeatureFlagDemo/FeatureFlagDemo.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
diff --git a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs b/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs
deleted file mode 100644
index 9f9c8964..00000000
--- a/examples/FeatureFlagDemo/HttpContextTargetingContextAccessor.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-//
-using Microsoft.AspNetCore.Http;
-using Microsoft.FeatureManagement.FeatureFilters;
-using System;
-using System.Collections.Generic;
-using System.Security.Claims;
-using System.Threading.Tasks;
-
-namespace FeatureFlagDemo
-{
- ///
- /// Provides an implementation of that creates a targeting context using info from the current HTTP request.
- ///
- public class HttpContextTargetingContextAccessor : ITargetingContextAccessor
- {
- private const string TargetingContextLookup = "HttpContextTargetingContextAccessor.TargetingContext";
- private readonly IHttpContextAccessor _httpContextAccessor;
-
- public HttpContextTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
- {
- _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
- }
-
- public ValueTask GetContextAsync()
- {
- HttpContext httpContext = _httpContextAccessor.HttpContext;
-
- //
- // Try cache lookup
- if (httpContext.Items.TryGetValue(TargetingContextLookup, out object value))
- {
- return new ValueTask((TargetingContext)value);
- }
-
- ClaimsPrincipal user = httpContext.User;
-
- List groups = new List();
-
- //
- // This application expects groups to be specified in the user's claims
- foreach (Claim claim in user.Claims)
- {
- if (claim.Type == ClaimTypes.GroupName)
- {
- groups.Add(claim.Value);
- }
- }
-
- //
- // Build targeting context based off user info
- TargetingContext targetingContext = new TargetingContext
- {
- UserId = user.Identity.Name,
- Groups = groups
- };
-
- //
- // Cache for subsequent lookup
- httpContext.Items[TargetingContextLookup] = targetingContext;
-
- return new ValueTask(targetingContext);
- }
- }
-}
diff --git a/examples/FeatureFlagDemo/Models/ErrorViewModel.cs b/examples/FeatureFlagDemo/Models/ErrorViewModel.cs
index 17542f32..bd9dea6c 100644
--- a/examples/FeatureFlagDemo/Models/ErrorViewModel.cs
+++ b/examples/FeatureFlagDemo/Models/ErrorViewModel.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System;
-
namespace FeatureFlagDemo.Models
{
public class ErrorViewModel
@@ -11,4 +9,4 @@ public class ErrorViewModel
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
-}
\ No newline at end of file
+}
diff --git a/examples/FeatureFlagDemo/Startup.cs b/examples/FeatureFlagDemo/Startup.cs
index 4c5722bd..f29f4934 100644
--- a/examples/FeatureFlagDemo/Startup.cs
+++ b/examples/FeatureFlagDemo/Startup.cs
@@ -45,7 +45,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddFeatureManagement()
.AddFeatureFilter()
- .WithTargeting()
+ .WithTargeting()
.UseDisabledFeaturesHandler(new FeatureNotEnabledDisabledHandler());
services.AddMvc(o =>
diff --git a/examples/FeatureFlagDemo/SuperUserFilter.cs b/examples/FeatureFlagDemo/SuperUserFilter.cs
index 25dc8e5f..4174c3c1 100644
--- a/examples/FeatureFlagDemo/SuperUserFilter.cs
+++ b/examples/FeatureFlagDemo/SuperUserFilter.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license.
//
using Microsoft.FeatureManagement;
-using System.Threading.Tasks;
namespace FeatureFlagDemo.FeatureManagement.FeatureFilters
{
diff --git a/examples/FeatureFlagDemo/ThirdPartyActionFilter.cs b/examples/FeatureFlagDemo/ThirdPartyActionFilter.cs
index a2d3abd1..4fb4c550 100644
--- a/examples/FeatureFlagDemo/ThirdPartyActionFilter.cs
+++ b/examples/FeatureFlagDemo/ThirdPartyActionFilter.cs
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
-using Microsoft.Extensions.Logging;
namespace FeatureFlagDemo
{
diff --git a/examples/FeatureFlagDemo/ThirdPartyMiddleware.cs b/examples/FeatureFlagDemo/ThirdPartyMiddleware.cs
index 74907908..52bdeddf 100644
--- a/examples/FeatureFlagDemo/ThirdPartyMiddleware.cs
+++ b/examples/FeatureFlagDemo/ThirdPartyMiddleware.cs
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Logging;
-using System.Threading.Tasks;
-
namespace FeatureFlagDemo
{
public class ThirdPartyMiddleware
diff --git a/examples/FeatureFlagDemo/Views/Shared/Error.cshtml.cs b/examples/FeatureFlagDemo/Views/Shared/Error.cshtml.cs
index d832e108..79b17264 100644
--- a/examples/FeatureFlagDemo/Views/Shared/Error.cshtml.cs
+++ b/examples/FeatureFlagDemo/Views/Shared/Error.cshtml.cs
@@ -24,4 +24,4 @@ public void OnGet()
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/RazorPages/Pages/Error.cshtml.cs b/examples/RazorPages/Pages/Error.cshtml.cs
index 74050d52..149f1f23 100644
--- a/examples/RazorPages/Pages/Error.cshtml.cs
+++ b/examples/RazorPages/Pages/Error.cshtml.cs
@@ -24,4 +24,4 @@ public void OnGet()
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/RazorPages/Pages/Index.cshtml.cs b/examples/RazorPages/Pages/Index.cshtml.cs
index 05dd8805..55bed039 100644
--- a/examples/RazorPages/Pages/Index.cshtml.cs
+++ b/examples/RazorPages/Pages/Index.cshtml.cs
@@ -18,4 +18,4 @@ public void OnGet()
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/RazorPages/Pages/Privacy.cshtml.cs b/examples/RazorPages/Pages/Privacy.cshtml.cs
index c1cd207f..cf5897c5 100644
--- a/examples/RazorPages/Pages/Privacy.cshtml.cs
+++ b/examples/RazorPages/Pages/Privacy.cshtml.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPages.Pages
{
@@ -16,4 +15,4 @@ public void OnGet()
{
}
}
-}
\ No newline at end of file
+}
diff --git a/examples/TargetingConsoleApp/Identity/IUserRepository.cs b/examples/TargetingConsoleApp/Identity/IUserRepository.cs
index 15fedc84..ea5a99f4 100644
--- a/examples/TargetingConsoleApp/Identity/IUserRepository.cs
+++ b/examples/TargetingConsoleApp/Identity/IUserRepository.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Threading.Tasks;
-
namespace TargetingConsoleApp.Identity
{
interface IUserRepository
diff --git a/examples/TargetingConsoleApp/Identity/InMemoryUserRepository.cs b/examples/TargetingConsoleApp/Identity/InMemoryUserRepository.cs
index 450c7044..1159bda0 100644
--- a/examples/TargetingConsoleApp/Identity/InMemoryUserRepository.cs
+++ b/examples/TargetingConsoleApp/Identity/InMemoryUserRepository.cs
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
namespace TargetingConsoleApp.Identity
{
class InMemoryUserRepository : IUserRepository
diff --git a/examples/TargetingConsoleApp/Identity/User.cs b/examples/TargetingConsoleApp/Identity/User.cs
index be27cac1..de1f4069 100644
--- a/examples/TargetingConsoleApp/Identity/User.cs
+++ b/examples/TargetingConsoleApp/Identity/User.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Collections.Generic;
-
namespace TargetingConsoleApp.Identity
{
class User
diff --git a/examples/TargetingConsoleApp/Program.cs b/examples/TargetingConsoleApp/Program.cs
index 5b89807a..d8ee6359 100644
--- a/examples/TargetingConsoleApp/Program.cs
+++ b/examples/TargetingConsoleApp/Program.cs
@@ -54,4 +54,4 @@
// Output results
Console.WriteLine($"The {FeatureName} feature is {(enabled ? "enabled" : "disabled")} for the user '{userId}'.");
}
-}
\ No newline at end of file
+}
diff --git a/examples/TargetingConsoleApp/TargetingConsoleApp.csproj b/examples/TargetingConsoleApp/TargetingConsoleApp.csproj
index e1ffbd53..fe29b948 100644
--- a/examples/TargetingConsoleApp/TargetingConsoleApp.csproj
+++ b/examples/TargetingConsoleApp/TargetingConsoleApp.csproj
@@ -5,7 +5,7 @@
net6.0
enable
-
+
@@ -20,4 +20,5 @@
Always
+
diff --git a/examples/VariantServiceDemo/Pages/Error.cshtml.cs b/examples/VariantServiceDemo/Pages/Error.cshtml.cs
index ea241507..4c6dfd88 100644
--- a/examples/VariantServiceDemo/Pages/Error.cshtml.cs
+++ b/examples/VariantServiceDemo/Pages/Error.cshtml.cs
@@ -24,5 +24,4 @@ public void OnGet()
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
-
}
diff --git a/examples/VariantServiceDemo/Pages/Privacy.cshtml.cs b/examples/VariantServiceDemo/Pages/Privacy.cshtml.cs
index 9a1c3334..374e9bde 100644
--- a/examples/VariantServiceDemo/Pages/Privacy.cshtml.cs
+++ b/examples/VariantServiceDemo/Pages/Privacy.cshtml.cs
@@ -16,5 +16,4 @@ public void OnGet()
{
}
}
-
}
diff --git a/examples/VariantServiceDemo/Program.cs b/examples/VariantServiceDemo/Program.cs
index 89e17c2c..843cd1ce 100644
--- a/examples/VariantServiceDemo/Program.cs
+++ b/examples/VariantServiceDemo/Program.cs
@@ -1,11 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using VariantServiceDemo;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.Telemetry.ApplicationInsights;
-
+using VariantServiceDemo;
var builder = WebApplication.CreateBuilder(args);
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs b/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs
index 742b942e..9091a4b0 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/AspNetCoreFeatureManagementBuilderExtensions.cs
@@ -3,9 +3,12 @@
//
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.FeatureManagement.FeatureFilters;
using Microsoft.FeatureManagement.Mvc;
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.FeatureManagement
{
@@ -44,5 +47,28 @@ public static IFeatureManagementBuilder UseDisabledFeaturesHandler(this IFeature
return builder;
}
+
+ ///
+ /// Enables the use of targeting within the application and adds a targeting context accessor that extracts targeting details from a request's HTTP context.
+ ///
+ /// The used to customize feature management functionality.
+ /// A that can be used to customize feature management functionality.
+ public static IFeatureManagementBuilder WithTargeting(this IFeatureManagementBuilder builder)
+ {
+ //
+ // Register the targeting context accessor with the same lifetime as the feature manager
+ if (builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IFeatureManager) && descriptor.Lifetime == ServiceLifetime.Scoped))
+ {
+ builder.Services.TryAddScoped();
+ }
+ else
+ {
+ builder.Services.TryAddSingleton();
+ }
+
+ builder.AddFeatureFilter();
+
+ return builder;
+ }
}
}
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs b/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs
new file mode 100644
index 00000000..f2fe6201
--- /dev/null
+++ b/src/Microsoft.FeatureManagement.AspNetCore/DefaultHttpTargetingContextAccessor.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+//
+using Microsoft.AspNetCore.Http;
+using Microsoft.FeatureManagement.FeatureFilters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.FeatureManagement
+{
+ ///
+ /// Provides a default implementation of that creates using info from the current HTTP request.
+ ///
+ internal sealed class DefaultHttpTargetingContextAccessor : ITargetingContextAccessor
+ {
+ ///
+ /// The key used to store and retrieve the from the items.
+ ///
+ private static object _cacheKey = new object();
+
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Creates an instance of the DefaultHttpTargetingContextAccessor
+ ///
+ public DefaultHttpTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
+ {
+ _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
+ }
+
+ ///
+ /// Gets from the current HTTP request.
+ ///
+ public ValueTask GetContextAsync()
+ {
+ HttpContext httpContext = _httpContextAccessor.HttpContext;
+
+ //
+ // Try cache lookup
+ if (httpContext.Items.TryGetValue(_cacheKey, out object value))
+ {
+ return new ValueTask((TargetingContext)value);
+ }
+
+ //
+ // Treat user identity name as user id
+ ClaimsPrincipal user = httpContext.User;
+
+ string userId = user?.Identity?.Name;
+
+ //
+ // Treat claims of type Role as groups
+ IEnumerable groups = httpContext.User.Claims
+ .Where(c => c.Type == ClaimTypes.Role)
+ .Select(c => c.Value)
+ .ToList();
+
+ TargetingContext targetingContext = new TargetingContext
+ {
+ UserId = userId,
+ Groups = groups
+ };
+
+ //
+ // Cache for subsequent lookup
+ httpContext.Items[_cacheKey] = targetingContext;
+
+ return new ValueTask(targetingContext);
+ }
+ }
+}
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs
index fb15e5b1..caf30a28 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs
@@ -106,9 +106,9 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context
//
// Enabled state is determined by either 'any' or 'all' features being enabled.
- bool enabled = RequirementType == RequirementType.All ?
- await Features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false)) :
- await Features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false));
+ bool enabled = RequirementType == RequirementType.All
+ ? await Features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false))
+ : await Features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false));
if (enabled)
{
@@ -134,9 +134,9 @@ public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext contex
//
// Enabled state is determined by either 'any' or 'all' features being enabled.
- bool enabled = RequirementType == RequirementType.All ?
- await Features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false)) :
- await Features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false));
+ bool enabled = RequirementType == RequirementType.All
+ ? await Features.All(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false))
+ : await Features.Any(async feature => await fm.IsEnabledAsync(feature).ConfigureAwait(false));
if (enabled)
{
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
index 04878e63..bdafc42d 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
+++ b/src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj
@@ -1,4 +1,5 @@
-
+
+
@@ -31,7 +32,7 @@
https://aka.ms/AzureAppConfigurationPackageIcon
© Microsoft Corporation. All rights reserved.
-
+
diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs
index 3eff1ad5..f65e62e8 100644
--- a/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs
+++ b/src/Microsoft.FeatureManagement.AspNetCore/TagHelpers/FeatureTagHelper.cs
@@ -63,9 +63,9 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
if (string.IsNullOrEmpty(Variant))
{
- enabled = Requirement == RequirementType.All ?
- await features.All(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false)) :
- await features.Any(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false));
+ enabled = Requirement == RequirementType.All
+ ? await features.All(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false))
+ : await features.Any(async feature => await _featureManager.IsEnabledAsync(feature).ConfigureAwait(false));
}
else
{
@@ -82,10 +82,11 @@ await features.All(async feature => await _featureManager.IsEnabledAsync(feature
}
enabled = await variants.Any(
- async variant => {
+ async variant =>
+ {
Variant assignedVariant = await _featureManager.GetVariantAsync(features.First()).ConfigureAwait(false);
- return variant == assignedVariant?.Name;
+ return variant == assignedVariant?.Name;
});
}
}
diff --git a/src/Microsoft.FeatureManagement/AssemblyInfo.cs b/src/Microsoft.FeatureManagement/AssemblyInfo.cs
index 955518ef..75bfb2fa 100644
--- a/src/Microsoft.FeatureManagement/AssemblyInfo.cs
+++ b/src/Microsoft.FeatureManagement/AssemblyInfo.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Runtime.CompilerServices;
@@ -9,4 +9,4 @@
"3ae70fbea5662f61dd9d640de2205b7bd5359a43dda006e51d83d1f5f7a7d3f849267a0a28676d" +
"cf49727a32487d4c75c4aacd5febb0069e1adc66ec63bbd18ec2276091a0e3c1326aa626c9e4db" +
"800714a134f2a81e405f35752b55220021923429cb61776cd2fa66d25c335f8dc27bb92292905a" +
-"3798d896")]
\ No newline at end of file
+"3798d896")]
diff --git a/src/Microsoft.FeatureManagement/ConfigurationWrapper.cs b/src/Microsoft.FeatureManagement/ConfigurationWrapper.cs
index 99f0b2a6..61d921f5 100644
--- a/src/Microsoft.FeatureManagement/ConfigurationWrapper.cs
+++ b/src/Microsoft.FeatureManagement/ConfigurationWrapper.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System;
-using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
+using System;
+using System.Collections.Generic;
namespace Microsoft.FeatureManagement
{
@@ -27,13 +27,13 @@ public string this[string key]
set => _configuration[key] = value;
}
- public IEnumerable GetChildren() =>
- _configuration.GetChildren();
+ public IEnumerable GetChildren()
+ => _configuration.GetChildren();
- public IChangeToken GetReloadToken() =>
- _configuration.GetReloadToken();
+ public IChangeToken GetReloadToken()
+ => _configuration.GetReloadToken();
- public IConfigurationSection GetSection(string key) =>
- _configuration.GetSection(key);
+ public IConfigurationSection GetSection(string key)
+ => _configuration.GetSection(key);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/ISystemClock.cs b/src/Microsoft.FeatureManagement/FeatureFilters/ISystemClock.cs
index 50c6743d..1fc9b667 100644
--- a/src/Microsoft.FeatureManagement/FeatureFilters/ISystemClock.cs
+++ b/src/Microsoft.FeatureManagement/FeatureFilters/ISystemClock.cs
@@ -17,4 +17,4 @@ internal interface ISystemClock
///
public DateTimeOffset UtcNow { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.FeatureManagement/FeatureFilters/Recurrence/RecurrenceEvaluator.cs b/src/Microsoft.FeatureManagement/FeatureFilters/Recurrence/RecurrenceEvaluator.cs
index 4dba080a..5e8b6872 100644
--- a/src/Microsoft.FeatureManagement/FeatureFilters/Recurrence/RecurrenceEvaluator.cs
+++ b/src/Microsoft.FeatureManagement/FeatureFilters/Recurrence/RecurrenceEvaluator.cs
@@ -339,7 +339,6 @@ private static int CalculateWeeklyDayOffset(DayOfWeek day1, DayOfWeek day2)
return ((int)day1 - (int)day2 + DaysPerWeek) % DaysPerWeek;
}
-
///
/// Sorts a collection of days of week based on their offsets from a specified first day of week.
/// A collection of days of week.
diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs
index 2b2af56c..ab07a595 100644
--- a/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs
+++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilder.cs
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.FeatureManagement.FeatureFilters;
using System;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.FeatureManagement.FeatureFilters;
namespace Microsoft.FeatureManagement
{
@@ -35,7 +35,7 @@ public IFeatureManagementBuilder AddFeatureFilter() where T : IFeatureFilterM
}
IEnumerable featureFilterImplementations = implementationType.GetInterfaces()
- .Where(i => i == typeof(IFeatureFilter) ||
+ .Where(i => i == typeof(IFeatureFilter) ||
(i.IsGenericType && i.GetGenericTypeDefinition().IsAssignableFrom(typeof(IContextualFeatureFilter<>))));
if (featureFilterImplementations.Count() > 1)
diff --git a/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs b/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs
index 93cb60df..70a91977 100644
--- a/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs
+++ b/src/Microsoft.FeatureManagement/FeatureManagementBuilderExtensions.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.FeatureManagement.FeatureFilters;
using Microsoft.FeatureManagement.Telemetry;
@@ -53,7 +53,7 @@ public static IFeatureManagementBuilder WithVariantService(this IFeatu
{
throw new ArgumentNullException(nameof(featureName));
}
-
+
if (builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IVariantServiceProvider)))
{
throw new InvalidOperationException($"A variant service of {typeof(TService).FullName} has already been added.");
diff --git a/src/Microsoft.FeatureManagement/FeatureManager.cs b/src/Microsoft.FeatureManagement/FeatureManager.cs
index a601f22e..40a0f0a7 100644
--- a/src/Microsoft.FeatureManagement/FeatureManager.cs
+++ b/src/Microsoft.FeatureManagement/FeatureManager.cs
@@ -35,7 +35,6 @@ public sealed class FeatureManager : IFeatureManager, IVariantFeatureManager
private IEnumerable _sessionManagers;
private TargetingEvaluationOptions _assignerOptions;
-
///
/// The activity source for feature management.
///
@@ -291,9 +290,9 @@ private async ValueTask EvaluateFeature(string featur
{
if (evaluationEvent.FeatureDefinition.Allocation == null)
{
- evaluationEvent.VariantAssignmentReason = evaluationEvent.Enabled ?
- VariantAssignmentReason.DefaultWhenEnabled :
- VariantAssignmentReason.DefaultWhenDisabled;
+ evaluationEvent.VariantAssignmentReason = evaluationEvent.Enabled
+ ? VariantAssignmentReason.DefaultWhenEnabled
+ : VariantAssignmentReason.DefaultWhenDisabled;
}
else if (!evaluationEvent.Enabled)
{
@@ -486,6 +485,7 @@ private async ValueTask IsEnabledAsync(FeatureDefinition feature
if (featureDefinition.RequirementType == RequirementType.Any)
{
enabled = true;
+
break;
}
diff --git a/src/Microsoft.FeatureManagement/IFilterParametersBinder.cs b/src/Microsoft.FeatureManagement/IFilterParametersBinder.cs
index bd572e71..fe889893 100644
--- a/src/Microsoft.FeatureManagement/IFilterParametersBinder.cs
+++ b/src/Microsoft.FeatureManagement/IFilterParametersBinder.cs
@@ -18,4 +18,4 @@ public interface IFilterParametersBinder
/// A settings object that is understood by the implementer of .
object BindParameters(IConfiguration parameters);
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs
index bd8c665b..0b78a237 100644
--- a/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs
+++ b/src/Microsoft.FeatureManagement/IVariantFeatureManager.cs
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Threading.Tasks;
-using System.Threading;
using Microsoft.FeatureManagement.FeatureFilters;
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace Microsoft.FeatureManagement
{
diff --git a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
index 5c0dce5d..5ffcb495 100644
--- a/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
+++ b/src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj
@@ -1,4 +1,5 @@
-
+
+
diff --git a/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs b/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs
index 1272b33c..f07bc694 100644
--- a/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs
+++ b/src/Microsoft.FeatureManagement/MicrosoftFeatureManagementFields.cs
@@ -49,4 +49,4 @@ internal static class MicrosoftFeatureManagementFields
public const string Telemetry = "telemetry";
public const string Metadata = "metadata";
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
index 346a0016..41149673 100644
--- a/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
+++ b/src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
@@ -66,7 +66,7 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec
services.TryAddScoped(sp => sp.GetRequiredService());
var builder = new FeatureManagementBuilder(services);
-
+
//
// Add built-in feature filters
builder.AddFeatureFilter();
@@ -158,7 +158,7 @@ public static IFeatureManagementBuilder AddScopedFeatureManagement(this IService
// Add built-in feature filters
builder.AddFeatureFilter();
- builder.AddFeatureFilter(sp =>
+ builder.AddFeatureFilter(sp =>
new TimeWindowFilter()
{
Cache = sp.GetRequiredService()
diff --git a/src/Microsoft.FeatureManagement/Targeting/TargetingEvaluator.cs b/src/Microsoft.FeatureManagement/Targeting/TargetingEvaluator.cs
index e642c01c..9051d761 100644
--- a/src/Microsoft.FeatureManagement/Targeting/TargetingEvaluator.cs
+++ b/src/Microsoft.FeatureManagement/Targeting/TargetingEvaluator.cs
@@ -12,10 +12,10 @@ namespace Microsoft.FeatureManagement.Targeting
{
static class TargetingEvaluator
{
- private static StringComparison GetComparisonType(bool ignoreCase) =>
- ignoreCase ?
- StringComparison.OrdinalIgnoreCase :
- StringComparison.Ordinal;
+ private static StringComparison GetComparisonType(bool ignoreCase)
+ => ignoreCase
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal;
const string OutOfRange = "The value is out of the accepted range.";
const string RequiredParameter = "Value cannot be null.";
@@ -189,9 +189,9 @@ public static bool IsTargeted(
if (sourceGroups != null)
{
- IEnumerable normalizedGroups = ignoreCase ?
- sourceGroups.Select(g => g?.ToLower()) :
- sourceGroups;
+ IEnumerable normalizedGroups = ignoreCase
+ ? sourceGroups.Select(g => g?.ToLower())
+ : sourceGroups;
foreach (string group in normalizedGroups)
{
@@ -231,15 +231,15 @@ public static bool IsTargeted(
throw new ArgumentNullException(nameof(hint));
}
- string userId = ignoreCase ?
- targetingContext.UserId?.ToLower() :
- targetingContext.UserId;
+ string userId = ignoreCase
+ ? targetingContext.UserId?.ToLower()
+ : targetingContext.UserId;
if (targetingContext.Groups != null)
{
- IEnumerable normalizedGroups = ignoreCase ?
- targetingContext.Groups.Select(g => g?.ToLower()) :
- targetingContext.Groups;
+ IEnumerable normalizedGroups = ignoreCase
+ ? targetingContext.Groups.Select(g => g?.ToLower())
+ : targetingContext.Groups;
foreach (string group in normalizedGroups)
{
@@ -279,9 +279,9 @@ public static bool IsTargeted(
throw new ArgumentNullException(nameof(hint));
}
- string userId = ignoreCase ?
- targetingContext.UserId?.ToLower() :
- targetingContext.UserId;
+ string userId = ignoreCase
+ ? targetingContext.UserId?.ToLower()
+ : targetingContext.UserId;
string defaultContextId = $"{userId}\n{hint}";
@@ -318,9 +318,9 @@ public static bool IsTargeted(ITargetingContext targetingContext, double from, d
throw new ArgumentException($"Value of {nameof(from)} cannot be larger than value of {nameof(to)}.");
}
- string userId = ignoreCase ?
- targetingContext.UserId?.ToLower() :
- targetingContext.UserId;
+ string userId = ignoreCase
+ ? targetingContext.UserId?.ToLower()
+ : targetingContext.UserId;
string contextId = $"{userId}\n{hint}";
@@ -345,7 +345,6 @@ private static bool IsTargeted(string contextId, double from, double to)
hash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(contextId));
}
-
//
// Endianness check ensures the consistency of targeting evaluation result across different architectures
if (!BitConverter.IsLittleEndian)
diff --git a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
index 58ebcf01..dce12cfe 100644
--- a/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
+++ b/tests/Tests.FeatureManagement.AspNetCore/FeatureManagementAspNetCore.cs
@@ -9,7 +9,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
diff --git a/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs b/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs
index 0635a289..cbf617d5 100644
--- a/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs
+++ b/tests/Tests.FeatureManagement.AspNetCore/MvcFilter.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
+using System.Threading.Tasks;
namespace Tests.FeatureManagement.AspNetCore
{
diff --git a/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj
index b14bbfdb..47592cc5 100644
--- a/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj
+++ b/tests/Tests.FeatureManagement.AspNetCore/Tests.FeatureManagement.AspNetCore.csproj
@@ -1,4 +1,4 @@
-
+
net6.0;net7.0;net8.0
diff --git a/tests/Tests.FeatureManagement/CustomTargetingFilter.cs b/tests/Tests.FeatureManagement/CustomTargetingFilter.cs
index 36579146..a0bdbace 100644
--- a/tests/Tests.FeatureManagement/CustomTargetingFilter.cs
+++ b/tests/Tests.FeatureManagement/CustomTargetingFilter.cs
@@ -25,7 +25,7 @@ public CustomTargetingFilter(IOptions options, ILogg
public Task EvaluateAsync(FeatureFilterEvaluationContext context)
{
- return _contextualFilter.EvaluateAsync(context, new TargetingContext(){ UserId = "Jeff" });
+ return _contextualFilter.EvaluateAsync(context, new TargetingContext() { UserId = "Jeff" });
}
}
}
diff --git a/tests/Tests.FeatureManagement/FeatureManagementTest.cs b/tests/Tests.FeatureManagement/FeatureManagementTest.cs
index 77a422af..e1a6efe1 100644
--- a/tests/Tests.FeatureManagement/FeatureManagementTest.cs
+++ b/tests/Tests.FeatureManagement/FeatureManagementTest.cs
@@ -1857,4 +1857,4 @@ public async Task TelemetryPublishing()
Assert.True(result);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Tests.FeatureManagement/RecurrenceEvaluation.cs b/tests/Tests.FeatureManagement/RecurrenceEvaluation.cs
index 50c6e449..184f3612 100644
--- a/tests/Tests.FeatureManagement/RecurrenceEvaluation.cs
+++ b/tests/Tests.FeatureManagement/RecurrenceEvaluation.cs
@@ -594,7 +594,7 @@ public void MatchDailyRecurrenceTest()
Range = new RecurrenceRange()
}
},
- false ),
+ false )
};
ConsumeEvaluationTestData(testData);
@@ -1671,7 +1671,7 @@ public async void RecurrenceEvaluationThroughCacheTest()
mockedTimeProvider.UtcNow = DateTimeOffset.Parse("2024-2-7T00:00:00+08:00");
Assert.False(await mockedTimeWindowFilter.EvaluateAsync(context));
- for (int i = 0; i < 10; i++ )
+ for (int i = 0; i < 10; i++)
{
mockedTimeProvider.UtcNow = mockedTimeProvider.UtcNow.AddDays(1);
Assert.False(await mockedTimeWindowFilter.EvaluateAsync(context));
diff --git a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj
index f3d7f100..367b945e 100644
--- a/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj
+++ b/tests/Tests.FeatureManagement/Tests.FeatureManagement.csproj
@@ -1,4 +1,4 @@
-
+
net48;net6.0;net7.0;net8.0