Custom Attribute settings in .net Core

10,371

Solution 1

Alternatively, you could extract the creation of featureNames into an injectable service (registered to DI) and use your attribute as a type filter or with IFilterFactory.

Using type filters, you would create your attribute as:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureFlagAttribute : ActionFilterAttribute
{
    private readonly string _featureName;
    private readonly IFeatureService _featureService;

    public FeatureFlagAttribute(string featureName, IFeatureService featureService)
    {
        _featureName = featureName;
        _featureService = featureService;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var features = _featureService.GetMyFeatures();
        var found = features.TryGetValue(_featureName, out var result);

        if (!found || !result)
        {
            // don't continue
            context.HttpContext.Response.StatusCode = 403;
        }
    }
}

In the constructor parameters, featureName stays the same, and needs to be defined to the attribute, while featureService will get resolved from the DI, so you need to register an implementation for this in your startup's ConfigureServices().

The attribute usage changes a bit with type filters, for example:

[TypeFilter(typeof(FeatureFlagAttribute), Arguments = new object[] { "feature-A" })]
public IActionResult Index()
{
    return View();
}

You can read more options of injecting dependencies into filters in the docs: https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection

Solution 2

A different approach, but maybe move that out of the attribute, perhaps using a static event as the API hook? then you can put the dictionary where-ever you want?

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class FeatureFlagAttribute : ActionFilterAttribute
{
    public FeatureFlagAttribute(string featureName)
    {
        selectedFeature = featureName;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (IsActive?.Invoke(selectedFeature) == false)
        {
            // dont continue
            context.HttpContext.Response.StatusCode = 403;
        }
    }
    public static event Func<string, bool> IsActive;
}

(note that you need to be careful with static events not to cause memory leaks)


Alternatively, keep what you have, but make the dictionary static (and thread-protected, etc); then add some kind of API like:

public static void SetFeatureEnabled(string featureName, bool enabled);

that tweaks the static dictionary.

Share:
10,371
Rob McCabe
Author by

Rob McCabe

Software developer from Northern Ireland. I use technologies such as .net Core, Angular, Docker, Kubernetes and much more!

Updated on June 29, 2022

Comments

  • Rob McCabe
    Rob McCabe almost 2 years

    I am writing a very simple custom attribute to be used with my methods for ASP.net Core. The attribute is to handle feature flags which indicate an endpoint method is "switched on or off" as follows:

    1) If a feature is turned ON, allow the code to pass through to the method and execute it as normal. 2) If the feature is turned OFF, just return from the attribute and dont execute the method within

    I was thinking something along the lines of this:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class FeatureFlagAttribute : ActionFilterAttribute
    {
        private Dictionary<string, bool> myFeaturesList;
        private readonly string selectedFeature;
    
        public FeatureFlagAttribute(string featureName)
        {
            selectedFeature = featureName;
        }
    
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var found = myFeaturesList.TryGetValue(selectedFeature, out var result);
    
            if (!found || !result)
            {
                // dont continue
                context.HttpContext.Response.StatusCode = 403;
            }
        }
    }
    

    I need the myFeaturesList populated for this to work BUT I dont want to pass it into the constructor every time this is being used. Whats the best way to configure this? I was thinking of setting a static property in the attribute but thought this was a bit of a lame approach and that there must be a better way. Thanks in advance!