Skip to content

Extending FeatureSwitch library

Valdis Iljuconoks edited this page Jan 10, 2015 · 6 revisions

Adding New Strategies

Currently FeatureSwitch comes with some built-in strategies. However it's natural that there is a need to extend FeatureSwitch and add new strategies. This is really easy to implement. FeatureSwitch recognizes two types of strategies:

  • Initializable: these strategies take part only during FeatureSwitch initialization routine and are called while feature set context is built-up. To create initializable strategy you need to implement FeatureSwitch.Strategies.IStrategy interface.
  • Read-only: read-only strategies are strategies that are able to read form the underlying storage initial value of the feature but not able to store or save it back. There will be disabled checkbox for features that are marked with these strategies in Asp.Net control panel. To create initializable strategy you need to implement FeatureSwitch.Strategies.IStrategyStorageReader interface.
  • Writeable: these strategies are able to update underlying storage and save new state of the feature. Features marked with these strategies will have enabled checkbox in Asp.Net control panel. To create initializable strategy you need to implement FeatureSwitch.Strategies.IStrategyStorageWriter interface.

In order to implement your new initializable strategy it’s enough to implement IStrategy interface.

public class EmptyStrategyImpl : IStrategy
{
    public void Initialize(ConfigurationContext configurationContext)
    {
    }
}

We will get back to ConfigurationContext a bit later. So as you can see this strategy is called when feature set context is built by passing configuration context to the Initialize method. In order to implement your new read-only strategy it’s enough to implement IStrategyStorageReader interface.

public class SampleReaderImpl : IStrategyStorageReader
{
    public void Initialize(ConfigurationContext configurationContext)
    {
        throw new System.NotImplementedException();
    }

    public bool Read()
    {
        throw new System.NotImplementedException();
    }
}

As you can see there is no feature identification mentioned in Read method. Identification key of the feature (one that is used in attribute definition).

[AppSettings(Key = "MySampleFeatureKey")]
public class MySampleFeature : BaseFeature
{

You can access feature key via ConfigurationContext.Key (that is passed in Initialize method).

To get rid of Initialize method and not bothering yourself to store configurationContext somewhere, you can inherit from FeatureSwitch.Strategies.Implementations.BaseStrategyReaderImpl. It will take care of this.

public class SampleReaderImpl : BaseStrategyReaderImpl
{
    public override bool Read()
    {
        throw new System.NotImplementedException();
    }
}

Adding New Writable Strategies

To create writable strategy you need to either implement IStrategyStorageWriter:

public class SampleWriterImpl : IStrategyStorageWriter
{
    public void Initialize(ConfigurationContext configurationContext)
    {
        throw new System.NotImplementedException();
    }

    public bool Read()
    {
        throw new System.NotImplementedException();
    }

    public void Write(bool state)
    {
        throw new System.NotImplementedException();
    }
}

Or you can inherit from FeatureSwitch.Strategies.BaseStrategyImpl to reduce code a bit:

public class SampleWriterImpl : BaseStrategyImpl
{
    public override bool Read()
    {
        throw new System.NotImplementedException();
    }

    public override void Write(bool state)
    {
        throw new System.NotImplementedException();
    }
}

Dependency Injection Support

Strategies are constructed using dependency injection services which means that strategies with constructor injections are supported out-of-box. So strategies like these will be constructed as long as necessary dependencies will be available in IoC (inversion of control) container.

private readonly ISampleInjectedInterface _sample;

public StrategyWithParameterReader(ISampleInjectedInterface sample)
{
    _sample = sample;
}

Enable Your Strategy

When new strategy has been implemented it’s important that you enable it. To enable new strategy you need to:

  • Create associated attribute that will be used to map your new strategy to particular feature marking that this feature is backed up by your strategy
  • Register this mapping in FeatureSwitch context.

So, to create mapping attribute – you need just new class inheriting from FeatureStrategyAttribute:

public class MyNewStrategy : FeatureStrategyAttribute
{
}

And you need to register new strategy implementation with particular attribute (if you are in EPiServer context – you can use EPiServer’s InitializableModule, or if you are on your own – you can use custom made initializable module to register this mapping):

var builder = new FeatureSetBuilder(new StructureMapDependencyContainer());
builder.Build(ctx =>
{
    ctx.ForStrategy<MyNewStrategy>().Use<MyNewStrategyImpl>();
});

And now you can use your new strategy on feature:

[MyNewStrategy(Key = "MySampleFeatureKey")]
public class MySampleFeature : BaseFeature
{

Starting from v1.3.1 there is no need to register strategy mapping inside FeatureSetContext. Now you can define mapping directly inside attribute:

public class MyNewStrategy : FeatureStrategyAttribute
{
    public override Type DefaultImplementation
    {
        get
        {
            return typeof(MyNewStrategyImpl);
        }
    }
}

Strategy registration in FeatureSetContext is still supported and is intended to be used for swapping already registered strategy implementation with new one.

Configuration Context of the Strategy

ConfigurationContext is something that is needed for the strategy to initialize properly and get some info about feature “attached” to. ConfigurationContext is passed when strategy is initialized during feature set context build process and passed to Initialize method:

public interface IStrategy
{
    void Initialize(ConfigurationContext configurationContext);
}

Currently there is no built-in support for customizing configuration context (providing ways to extend and hook into configuration context creation process), but this is something I’m looking into in future releases of FeatureSwitch library.

Putting it all together

So in summary let's implement IsLocalRequest feature to check either we need to disable or enable some sort of functionality for local requests (one sample could be - that you don't need to include tracking script inside your markup if request is local).

Create Feature

This is source code for feature itself:

[IsLocalRequestAttribute]
public class IsLocalRequest : BaseFeature
{
}

Mapping from strategy to its implementation

You need to provide a bit of metadata to map from strategy to its implementation. This is done via attribute's DefaultImplementation property (or via FeatureSetContext registration):

public class IsLocalRequestAttribute : FeatureStrategyAttribute
{
    public IsLocalRequestAttribute()
    {
        Key = "...";
    }

    public override Type DefaultImplementation
    {
        get
        {
            return typeof(IsLocalRequestStrategyImpl);
        }
    }
}

Using DefaultImplementation you can now more quickly add required mapping (previously override using ForStrategy<TStrategy>().Use<TImplementation>() was needed inside IoC container).

Strategy Implementation

And strategy implementation is pretty straight forward:

public class IsLocalRequestStrategyImpl : BaseStrategyReaderImpl
{
    public override bool Read()
    {
        return HttpContext.Current != null && HttpContext.Current.Request.IsLocal;
    }
}