Skip to content

Commit

Permalink
Update to .NET 7 and v1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
henkmollema committed Nov 9, 2022
1 parent 01a7c16 commit 10e79e0
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 210 deletions.
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '{build}'
pull_requests:
do_not_increment_build_number: true
image: Visual Studio 2019
image: Visual Studio 2022
nuget:
disable_publish_on_pr: true
build_script:
Expand Down
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: csharp
mono: none
dotnet: 3.1
dotnet: 7.0
dist: xenial
env:
global:
Expand All @@ -17,5 +17,5 @@ matrix:
- os: linux
dist: xenial
- os: osx
dotnet: 3.1.301
dotnet: 7.0.100
osx_image: xcode12
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,53 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;

namespace Webenable.FeatureToggles
namespace Webenable.FeatureToggles;

/// <summary>
/// Evaluates feature toggles against <see cref="IConfiguration"/> of the application.
/// </summary>
public class ConfigFeatureToggleConfiguration : FeatureToggleConfiguration
{
/// <summary>
/// Evaluates feature toggles against <see cref="IConfiguration"/> of the application.
/// </summary>
public class ConfigFeatureToggleConfiguration : FeatureToggleConfiguration
private readonly IConfiguration _configuration;

public ConfigFeatureToggleConfiguration(IConfiguration configuration)
{
_configuration = configuration;
}

public override ValueTask<bool?> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
{
private readonly IConfiguration _configuration;
// Resolve the feature section from the configuration.
var configKey = GetConfigKey(featureName);
var section = _configuration.GetSection(configKey);
if (section.Value != null)
{
// "Foo": true
return new ValueTask<bool?>(section.Get<bool>());
}

public ConfigFeatureToggleConfiguration(IConfiguration configuration)
if (section.GetSection("Enabled").Exists())
{
_configuration = configuration;
// Feature is explictly enabled or disabled.
// "Foo": {
// "Enabled": true,
// "Bar": { }
// }
return new ValueTask<bool?>(section.GetValue<bool>("Enabled"));
}

public override ValueTask<bool?> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
if (section.GetChildren().Any())
{
// Resolve the feature section from the configuration.
var configKey = GetConfigKey(featureName);
var section = _configuration.GetSection(configKey);
if (section.Value != null)
{
// "Foo": true
return new ValueTask<bool?>(section.Get<bool>());
}

if (section.GetSection("Enabled").Exists())
{
// Feature is explictly enabled or disabled.
// "Foo": {
// "Enabled": true,
// "Bar": { }
// }
return new ValueTask<bool?>(section.GetValue<bool>("Enabled"));
}

if (section.GetChildren().Any())
{
// Enable the feature if it contains children and is not explicitly enabled or disabled.
// "Foo": {
// "Bar": { }
// }
return new ValueTask<bool?>(true);
}

// The feature is not toggled in the configuration.
return new ValueTask<bool?>((bool?)null);
// Enable the feature if it contains children and is not explicitly enabled or disabled.
// "Foo": {
// "Bar": { }
// }
return new ValueTask<bool?>(true);
}

protected virtual string GetConfigKey(string featureName) => "Features:" + featureName.Replace('.', ':');
// The feature is not toggled in the configuration.
return new ValueTask<bool?>((bool?)null);
}

protected virtual string GetConfigKey(string featureName) => "Features:" + featureName.Replace('.', ':');
}
95 changes: 47 additions & 48 deletions src/Webenable.FeatureToggles/DatabaseFeatureToggleConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,67 @@
using Dommel;
using Microsoft.Extensions.Options;

namespace Webenable.FeatureToggles
namespace Webenable.FeatureToggles;

/// <summary>
/// Evaluates feature toggles against <see cref="FeatureToggleDto"/> records the database.
/// </summary>
public class DatabaseFeatureToggleConfiguration : FeatureToggleConfiguration
{
/// <summary>
/// Evaluates feature toggles against <see cref="FeatureToggleDto"/> records the database.
/// </summary>
public class DatabaseFeatureToggleConfiguration : FeatureToggleConfiguration
{
private readonly FeatureToggleOptions _options;
private readonly FeatureToggleOptions _options;

public DatabaseFeatureToggleConfiguration(IOptions<FeatureToggleOptions> options)
{
_options = options.Value;
}
public DatabaseFeatureToggleConfiguration(IOptions<FeatureToggleOptions> options)
{
_options = options.Value;
}

public override async ValueTask<bool?> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
public override async ValueTask<bool?> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
{
var factory = _options.DbConnectionFactory;
if (factory is not null)
{
var factory = _options.DbConnectionFactory;
if (factory is object)
using var con = await factory(cancellationToken);
var toggle = await con.FirstOrDefaultAsync<FeatureToggleDto>(x => x.Name == featureName, cancellationToken: cancellationToken);
if (toggle is not null)
{
using var con = await factory(cancellationToken);
var toggle = await con.FirstOrDefaultAsync<FeatureToggleDto>(x => x.Name == featureName);
if (toggle is object)
{
return toggle.State == FeatureToggleState.Enabled;
}
return toggle.State == FeatureToggleState.Enabled;
}

// No database connection configured or feature toggle is not configured
return null;
}

public override int Order => base.Order - 10;
// No database connection configured or feature toggle is not configured
return null;
}

public override int Order => base.Order - 10;
}

/// <summary>
/// A feature toggle record.
/// </summary>
[Table("FeatureToggles")]
public class FeatureToggleDto
{
/// <summary>
/// A feature toggle record.
/// Gets or sets the ID of the feature toggle record.
/// </summary>
[Table("FeatureToggles")]
public class FeatureToggleDto
{
/// <summary>
/// Gets or sets the ID of the feature toggle record.
/// </summary>
public int Id { get; set; }

/// <summary>
/// Gets or sets the name of the feature.
/// </summary>
public string? Name { get; set; }
public int Id { get; set; }

/// <summary>
/// Gets or sets the state of the feature toggle.
/// </summary>
public FeatureToggleState State { get; set; }
}
/// <summary>
/// Gets or sets the name of the feature.
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Specifies the state of the feature toggle.
/// Gets or sets the state of the feature toggle.
/// </summary>
public enum FeatureToggleState
{
Disabled = 0,
Enabled = 1,
}
public FeatureToggleState State { get; set; }
}

/// <summary>
/// Specifies the state of the feature toggle.
/// </summary>
public enum FeatureToggleState
{
Disabled = 0,
Enabled = 1,
}
93 changes: 46 additions & 47 deletions src/Webenable.FeatureToggles/FeatureRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,66 @@
using System.Threading;
using System.Threading.Tasks;

namespace Webenable.FeatureToggles
namespace Webenable.FeatureToggles;

/// <summary>
/// Evaluates whether features are enabled.
/// </summary>
public interface IFeatureRouter
{
/// <summary>
/// Evaluates whether features are enabled.
/// Evaluates whether a feature toggle with the specified <paramref name="featureName"/> is enabled.
/// </summary>
public interface IFeatureRouter
/// <param name="featureName">
/// The name of the feature. Nested feature toggles can be specified
/// using dots (e.g. <c>Webshop.ShoppingCart.ShareShoppingCart</c>.)</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to propagate cancellation.</param>
/// <returns><c>true</c> when the feature is enabled; otherwise, <c>false</c>.</returns>
ValueTask<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default);
}

/// <summary>
/// Default <see cref="IFeatureRouter"/> implementation which evaluates <see cref="IFeatureToggleConfiguration"/>'s.
///
/// <para>
/// All the <see cref="IFeatureToggleConfiguration"/> implementations are fetched and evaluated in their specified
/// <see cref="IFeatureToggleConfiguration.Order"/>. If a <see cref="IFeatureToggleConfiguration"/> returns either
/// <c>true</c> or <c>false</c> the evaluation is stopped and the value is returned. When a <see cref="IFeatureToggleConfiguration"/>
/// implementation does not returns a value, the next configuration is evaluated. In case no configuration configures
/// a certain feature toggle specified, <c>false</c> is returned.
/// </para>
/// </summary>
public class DefaultFeatureRouter : IFeatureRouter
{
private readonly IEnumerable<IFeatureToggleConfiguration> _featureToggleConfiguration;

public DefaultFeatureRouter(IEnumerable<IFeatureToggleConfiguration> featureToggleConfiguration)
{
/// <summary>
/// Evaluates whether a feature toggle with the specified <paramref name="featureName"/> is enabled.
/// </summary>
/// <param name="featureName">
/// The name of the feature. Nested feature toggles can be specified
/// using dots (e.g. <c>Webshop.ShoppingCart.ShareShoppingCart</c>.)</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to propagate cancellation.</param>
/// <returns><c>true</c> when the feature is enabled; otherwise, <c>false</c>.</returns>
ValueTask<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default);
_featureToggleConfiguration = featureToggleConfiguration;
}

/// <summary>
/// Default <see cref="IFeatureRouter"/> implementation which evaluates <see cref="IFeatureToggleConfiguration"/>'s.
///
/// <para>
/// All the <see cref="IFeatureToggleConfiguration"/> implementations are fetched and evaluated in their specified
/// <see cref="IFeatureToggleConfiguration.Order"/>. If a <see cref="IFeatureToggleConfiguration"/> returns either
/// <c>true</c> or <c>false</c> the evaluation is stopped and the value is returned. When a <see cref="IFeatureToggleConfiguration"/>
/// implementation does not returns a value, the next configuration is evaluated. In case no configuration configures
/// a certain feature toggle specified, <c>false</c> is returned.
/// </para>
/// </summary>
public class DefaultFeatureRouter : IFeatureRouter
/// <inheritdoc/>
public async ValueTask<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
{
private readonly IEnumerable<IFeatureToggleConfiguration> _featureToggleConfiguration;

public DefaultFeatureRouter(IEnumerable<IFeatureToggleConfiguration> featureToggleConfiguration)
if (string.IsNullOrEmpty(featureName))
{
_featureToggleConfiguration = featureToggleConfiguration;
throw new ArgumentNullException(nameof(featureName));
}

/// <inheritdoc/>
public async ValueTask<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
// The feature toggle providers are invoked in the specified order
foreach (var featureToggleConfiguration in _featureToggleConfiguration.OrderBy(f => f.Order))
{
if (string.IsNullOrEmpty(featureName))
{
throw new ArgumentNullException(nameof(featureName));
}
cancellationToken.ThrowIfCancellationRequested();

// The feature toggle providers are invoked in the specified order
foreach (var featureToggleConfiguration in _featureToggleConfiguration.OrderBy(f => f.Order))
// If the provider returned a value use it, otherwise keep recursing
var enabled = await featureToggleConfiguration.IsEnabledAsync(featureName, cancellationToken);
if (enabled.HasValue)
{
cancellationToken.ThrowIfCancellationRequested();

// If the provider returned a value use it, otherwise keep recursing
var enabled = await featureToggleConfiguration.IsEnabledAsync(featureName, cancellationToken);
if (enabled.HasValue)
{
return enabled.Value;
}
return enabled.Value;
}

// No provider configured this feature
return false;
}

// No provider configured this feature
return false;
}
}
Loading

0 comments on commit 10e79e0

Please sign in to comment.