Giter Site home page Giter Site logo

rimdev.featureflags's Introduction

RimDev.FeatureFlags

A library for strongly typed feature flags in ASP.NET Core.

Screenshot

Package Version
RimDev.AspNetCore.FeatureFlags RimDev.AspNetCore.FeatureFlags NuGet Version
RimDev.AspNetCore.FeatureFlags.UI RimDev.AspNetCore.FeatureFlags.UI NuGet Version

Installation

Install the RimDev.AspNetCore.FeatureFlags and (optional) RimDev.AspNetCore.FeatureFlags.UI NuGet packages.

> dotnet add package RimDev.AspNetCore.FeatureFlags
> dotnet add package RimDev.AspNetCore.FeatureFlags.UI

or

PM> Install-Package RimDev.AspNetCore.FeatureFlags
PM> Install-Package RimDev.AspNetCore.FeatureFlags.UI

Usage

You'll need to wire up Startup.cs as follows:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using RimDev.AspNetCore.FeatureFlags;

namespace MyApplication
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            var featureFlagsConnectionString
                = configuration.GetConnectionString("featureFlags");
            var featureFlagsInitializationConnectionString
                = configuration.GetConnectionString("featureFlagsInitialization");

            services.AddRimDevFeatureFlags(
                configuration,
                new[] { typeof(Startup).Assembly },
                connectionString: featureFlagsConnectionString,
                initializationConnectionString: featureFlagsInitializationConnectionString
                );

            // IFeatureManagerSnapshot should always be scoped / per-request lifetime
            services.AddScoped<IFeatureManagerSnapshot>(serviceProvider =>
            {
                var featureFlagSessionManager = serviceProvider.GetRequiredService<FeatureFlagsSessionManager>();
                var featureFlagsSettings = serviceProvider.GetRequiredService<FeatureFlagsSettings>();
                return new LussatiteLazyCacheFeatureManager(
                    featureFlagsSettings.FeatureFlagTypes.Select(x => x.Name).ToList(),
                    new []
                    {
                        // in other use cases, you might list multiple ISessionManager objects to have layers
                        featureFlagSessionManager
                    });
            });

            services.AddRimDevFeatureFlagsUi();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseFeatureFlags(options);

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                // IMPORTANT: Controlling access of the UI / API of this library is the responsibility of the user.
                // Apply authentication / authorization around the `UseFeatureFlagsUI` method as needed,
                // as this method wires up the various endpoints.
                endpoints.MapFeatureFlagsUI(options);
            });
        }
    }
}

Next, create feature flags like this in the assemblies passed to AddRimDevFeatureFlags():

using RimDev.AspNetCore.FeatureFlags;

namespace MyApplication
{
    [Description("My feature description.")] // Optional displays on the UI
    public class MyFeature : Feature
    {
        // Feature classes could include other static information if desired by your application.
    }
}

Now you can dependency inject any of your feature flags using the standard ASP.NET Core IoC!

public class MyController : Controller
{
    private readonly MyFeature myFeature;

    public MyController(MyFeature myFeature)
    {
        this.myFeature = myFeature;
    }

    // Use myFeature instance here, using myFeature.Value for the on/off toggle value.
}

UI

The UI wired up by UseFeatureFlagsUI is available by default at /_features. The UI and API endpoints can be modified in FeatureFlagUiSettings if you'd like, too.

License

MIT License

rimdev.featureflags's People

Contributors

andrewrady avatar atifaziz avatar billbogaiv avatar czy5074 avatar kendaleiv avatar khalidabuhakmeh avatar lightyeare avatar schilco-rimdev avatar sobieck avatar tedk13 avatar tgharold avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rimdev.featureflags's Issues

Feature Flags V2 Concepts/Ideas

FeatureFlags v2

The goal of FeatureFlags version 2, is to enable varying storage mechanisms for each feature. In addition, it introduces concepts like conditional enabling and feature parameters. This is a rough mind dump of the concepts needed to rewrite feature flags. Obviously, the implementation and ideas may change based on how we can get it done.

FeatureFlagOptions options = new FeatureFlagOptions()
    .AddStore("instance", new SqlFeatureStore(connectionString))
    .AddStore("session", new CookieSessionStore())
    // .AddStoreFactory(/* service provider */)
    .ConditionalFactory(/* service provider */)
    .FeatureFactory(/* service provider */);

public void ConfigureServices(ServiceColleciton services) {
    services.AddTransient(Feature);
    services.AddFeatureFlags();
    services.AddFeature<T>(
        /* allow you to skip attributes if you don't like them */
    )
}
public abstract class Feature {
    public bool Enabled { get;set; }
    public Parameters Parameters { get;set; }
}

public class Parameter {
    public string Name {get;set;}
    public string Value {get;set;}
    public T As<T>();
}

[Feature(
    name: "My Feature",
    description : "this is my feature",
    stores: new [] { "session", "instance" }
)]
public class MyFeature : Feature {}

[Feature(
    name: "My Label",
    description : "this is my feature",
    stores: new [] { "session", "instance" }
)]
[When(typeof(MyLabelTimeOfDayCondition, "", ""))]
public class MyFeature : Feature {
    public string Value {get;set;} = "What?!";
}

[Condition(
    name: "Time Of Day",
    description: "Changes the value of a string to refelect time of day",
    required: new [] { "", "" },
    optional: new [] { "", "" }
)]
public class MyLabelTimeOfDayCondition : Conditional {
    private ISystemClock clock;
    public MyLabelTimeOfDayCondition(ISystemClock clock) {
        this.clock = clock;
    }

    public override async Task Apply(Feature feature) {
        if (feature is MyFeature fv) {
            // this conditional always enables
            fv.Enabled = true;
            if (clock.Now.Hours <= 12) {
                fv.Value = "Good Morning!";
            } else {
                fv.Value = "Good Night!";
            }

            return;
        }

        feature.Enabled = false;
        return Task.FromResult(0);
    }
}

public abstract class Conditional {
    public abstract Task Apply(Feature feature);
}

public abstract class FeatureStore {
    public abstract Task Clear();
    public abstract Task Save<T>(T feature) where T: Feature;
    public abstract Task<T> Get<T>() where T: Feature;
}
Feature session instance cloud
My Feature On Off Off
My Label (conditions+) On On On
{
  "stores": [],
  "features": [
    {
      "id": "<fullname type>",
      "name": "My Feature",
      "description": "",
      "stores": [
        {
          "session": {
            "enabled": true
          },
          "instance": {
            "enabled": false
          },
          "cloud": {
            "enabled": false
          }
        }
      ]
    },
    {
      "id": "<fullname type>",
      "name": "My Label",
      "description": "",
      "conditions": [
        {
          "type": "<fullname type>",
          "name": "Time Of Day",
          "description": "",
          "parameters": [{ "name": "value" }]
        }
      ],
      "stores": [
        {
          "session": {
            "enabled": true,
            "value": "What?!"
          },
          "instance": {
            "enabled": true,
            "value": "What?!"
          },
          "cloud": {
            "enabled": true,
            "value": "What?!"
          }
        }
      ]
    }
  ]
}

How to get the set connection string to the static options?

There are probably lots of ways to do this but how do you recommend doing this?

Like the code below?

     public class Startup
    {
        private static readonly FeatureFlagOptions options 
            = new FeatureFlagOptions().UseCachedSqlFeatureProvider(Configuration.GetConnectionString("connectionString"));

	public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

Customer UiPath Error

I change UiPaht to "/featureflag", but the HTML loading main.js is fixed to "/_features"

image

How to secure the /_features endpoint?

I have been trying to figure out how to secure the endpoint but I unsure how to proceed.

This comment

IMPORTANT: Controlling access of the UI / API of this library is the responsibility of the user. Apply authentication / authorization around the UseFeatureFlagsUI method as needed, as this method wires up the various endpoints. app.UseFeatureFlagsUI(options);

I have tried things like

options.Conventions.AuthorizePage("/_features", "Administrator");

But that does not kick off autorization for this endpoint.
And I can´t add any

if(User.IsInRole(Constants.AdministratorsRole))

around app.UseFeatureFlagsUI(options); in Configure()

How would you do this?

v2 Feature Flags

🔧 Enhancement Request

A clear and concise description of the enhancement.

Who is asking for the feature?

What business problem is this hoping to solve?

Allow FeatureFlagsOptions to set registration-type of features

Currently, all features are registered as Transient. Instead of having to query the IFeatureProvider every time a feature is requested, something like Scoped would short-circuit this path (since the likelihood of the feature changing state during a request is low and could result in weird edge-cases).

An example use-case is a TagHelper. Injecting a feature into a helper and then using the helper in many places results in a lot of IFeatureProvider lookups.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.