Skip to content

Commit

Permalink
Use MEF v.1
Browse files Browse the repository at this point in the history
Uses MEF v.1 and adds User Behaviour.
  • Loading branch information
Boggin committed Dec 13, 2018
1 parent 70293be commit 44dfac3
Show file tree
Hide file tree
Showing 14 changed files with 399 additions and 1 deletion.
15 changes: 15 additions & 0 deletions EnabledBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,15 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours
{
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;

[Export(typeof(IBehaviour))]
public class EnabledBehaviour : IBehaviour
{
public Func<Dictionary<string, string>, bool> Behaviour()
{
return x => Convert.ToBoolean(x["enabled"]);
}
}
}
10 changes: 10 additions & 0 deletions ExampleFeatureFlagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,10 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Features
{
/*
* SSO (Single Sign On) will gradually become available so
* this flag is to manage the roll-out.
*/
public class ExampleFeatureFlagger : IFeatureFlagger
{
}
}
31 changes: 31 additions & 0 deletions Feature.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,31 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
using System.Collections.Generic;

public class Feature
{
public Feature(string name)
{
this.Flags = new List<Flag>();
this.Name = name;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Not inherited")]
public Feature(string name, List<Flag> flags)
{
this.Flags = flags;
this.Name = name;
}

public string Name { get; private set; }

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Not inherited")]
public List<Flag> Flags { get; }

public Feature Add(Flag flag)
{
this.Flags.Add(flag);
return this;
}
}
}
63 changes: 63 additions & 0 deletions FeatureFlagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,63 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Configuration;
using System.Reflection;

using RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "Naming is hard")]
public sealed class FeatureFlagger
{
static FeatureFlagger()
{
// ReSharper disable once ObjectCreationAsStatement
new FeatureFlagger();
}

private FeatureFlagger()
{
SetBehaviours();
SetFeatures();
}

[ImportMany]
public static IEnumerable<IBehaviour> Behaviours { get; private set; }

public static IEnumerable<Feature> Features { get; private set; }

private static void SetFeatures()
{
Features =
(List<Feature>)ConfigurationManager.GetSection("features");
}

private void SetBehaviours()
{
// wouldn't it be nice if Microsoft.Composition was available?
var catalog = new AggregateCatalog();
try
{
catalog.Catalogs.Add(
new AssemblyCatalog(
typeof(IBehaviour).GetTypeInfo().Assembly));
var container = new CompositionContainer(catalog);
try
{
container.SatisfyImportsOnce(this);
Behaviours = container.GetExportedValues<IBehaviour>();
}
finally
{
container.Dispose();
}
}
finally
{
catalog.Dispose();
}
}
}
}
45 changes: 45 additions & 0 deletions FeatureFlaggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,45 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
using System;
using System.Collections.Generic;
using System.Linq;

public static class FeatureFlaggerExtensions
{
public static bool IsEnabled(this IFeatureFlagger featureFlagger)
{
var behaviours = FeatureFlagger.Behaviours.ToList();
var flags = GetFlags(featureFlagger);

return !(from flag in flags
let behaviour =
behaviours.FirstOrDefault(
b =>
b.GetType()
.Name.ToUpperInvariant()
.Contains(flag.Name.ToUpperInvariant()))
let func = behaviour.Behaviour()
where func(flag.Properties) == false
select flag).Any();
}

private static IEnumerable<Flag> GetFlags(IFeatureFlagger featureFlagger)
{
var featureName =
featureFlagger.GetType().Name
.Replace("FeatureFlagger", string.Empty);
var feature =
FeatureFlagger.Features.ToList()
.Find(
f =>
f.Name.Equals(
featureName,
StringComparison.OrdinalIgnoreCase));

// always add the feature name to each flag as a property.
feature.Flags.ForEach(f => f.Add("feature", feature.Name));

return feature.Flags;
}
}
}
75 changes: 75 additions & 0 deletions FeaturesSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,75 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Xml;

public class FeaturesSection : IConfigurationSectionHandler
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "Standard method")]
public object Create(
object parent,
object configContext,
XmlNode section)
{
var features = new List<Feature>();

foreach (XmlNode childNode in section.ChildNodes)
{
var flag = EnabledFlag(childNode.Attributes);
var feature =
new Feature(
childNode.Attributes?.GetNamedItem("name").InnerText);
feature.Flags.Add(flag);

foreach (XmlNode node in childNode.ChildNodes)
{
if (node.Attributes == null)
{
continue;
}

var attrs =
node.Attributes.Cast<XmlAttribute>()
.ToDictionary(
attr => attr.Name,
attr => attr.InnerText);

// title case is required when looking up Behaviours.
var info = new CultureInfo("en-US").TextInfo;
var name = info.ToLower(node.Name);

feature.Flags.Add(new Flag(name, attrs));
}

features.Add(feature);
}

return features;
}

private static Flag EnabledFlag(XmlAttributeCollection attributes)
{
string enabled = null;
if (attributes != null)
{
enabled = attributes.GetNamedItem("enabled").InnerText;
}

if (string.IsNullOrWhiteSpace(enabled))
{
throw new ArgumentException("enabled attribute is required");
}

var flag =
new Flag(
"Enabled",
new Dictionary<string, string> { { "enabled", enabled } });

return flag;
}
}
}
29 changes: 29 additions & 0 deletions Flag.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,29 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
using System.Collections.Generic;

public class Flag
{
public Flag(string name)
{
this.Properties = new Dictionary<string, string>();
this.Name = name;
}

public Flag(string name, Dictionary<string, string> properties)
{
this.Properties = properties;
this.Name = name;
}

public string Name { get; private set; }

public Dictionary<string, string> Properties { get; }

public Flag Add(string key, string value)
{
this.Properties.Add(key, value);
return this;
}
}
}
15 changes: 15 additions & 0 deletions FromBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,15 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours
{
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;

[Export(typeof(IBehaviour))]
public class FromBehaviour : IBehaviour
{
public Func<Dictionary<string, string>, bool> Behaviour()
{
return x => DateTime.UtcNow.Date >= DateTime.Parse(x["date"]).Date;
}
}
}
10 changes: 10 additions & 0 deletions IBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,10 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours
{
using System;
using System.Collections.Generic;

public interface IBehaviour
{
Func<Dictionary<string, string>, bool> Behaviour();
}
}
6 changes: 6 additions & 0 deletions IFeatureFlagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,6 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger
{
public interface IFeatureFlagger
{
}
}
9 changes: 9 additions & 0 deletions IUser.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,9 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours
{
public interface IUser
{
string UserName();

bool UserHasFeature(string userName, string featureName);
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
# FeatureFlagger
# FeatureFlagger
![Image of Flag](https://github.com/Boggin/FeatureFlagger/raw/master/assets/flag-64x64.png) Yet Another Feature Flag (Feature Toggle / Feature Switch) implementation.

[![Build status](https://ci.appveyor.com/api/projects/status/f3ov0d8fqfy46yuu/branch/master?svg=true)](https://ci.appveyor.com/project/Boggin/featureflagger/branch/master)
Expand Down
45 changes: 45 additions & 0 deletions User.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,45 @@
namespace RoyalLondon.IntermediaryManagement.Api.FeatureFlagger.Behaviours
{
using System;
using System.ComponentModel.Composition;
using System.Linq;

using RoyalLondon.IntermediaryManagement.Api.Entities;

[Export(typeof(IUser))]
public class User : IUser
{
public bool UserHasFeature(string userName, string featureName)
{
IntermediaryManagement_DB context = null;
try
{
context = IntermediariesContainerFactory.Create();
var user =
context.Users.FirstOrDefault(
u =>
u.Name.Equals(
userName,
StringComparison.OrdinalIgnoreCase));
var hasFeature =
user?.Features.Any(
f =>
f.Name.Equals(
featureName,
StringComparison.OrdinalIgnoreCase));

return hasFeature == true;
}
finally
{
context?.Dispose();
}
}

public string UserName()
{
// TODO: get the authorised user from SSO.
return "dummy";
}
}
}
Loading

0 comments on commit 44dfac3

Please sign in to comment.