Dynamics CRM: Implement Feature Flags in Plugin

“Feature flags” is a mechanism to set the on/off for a feature without changing the code. You can learn more about this from a blog post here. With the Environment variable in place (which previously many organizations created their own Config Table), we can implement Feature flags in the Plugin. 

Fow chart

The idea is to make all the business logic across the plugins configurable via JSON string that we store in the Environment variable table. FYI, the below code still got lots of room for improvement and just for giving you the idea on how to do basic stuff. 😁

The Framework Code

Note: for bootstrapping the plugin project, you always can run “pac plugin init” (you must install Microsoft Power Platform CLI on your machine).

Below is the snippet for the code:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace DemoPlugin
{
    public interface IOperation
    {
        ILocalPluginContext LocalPluginContext { get; }
        IOrganizationService AdminService { get; }
        IOrganizationService Service { get; }
        void Execute();
    }

    [DataContract]
    public class EnvironmentDefinitionModel
    {
        [DataMember(Name = "Operations")]
        public EnvironmentOperationModel[] Operations { get; set; }
    }

    [DataContract]
    public class EnvironmentOperationModel
    {
        [DataMember(Name = "Name")]
        public string Name { get; set; }
        [DataMember(Name = "Enabled")]
        public bool Enabled { get; set; }
    }

    public abstract class Operation : IOperation
    {
        public ILocalPluginContext LocalPluginContext { get; internal set; }

        public IOrganizationService AdminService => LocalPluginContext.SystemUserService;

        public IOrganizationService Service => LocalPluginContext.CurrentUserService;

        public Operation(ILocalPluginContext localPluginContext)
        {
            LocalPluginContext = localPluginContext;
        }

        public void Execute()
        {
            var pluginName = Assembly.GetExecutingAssembly().GetName().Name;
            var pluginConfig = GetPluginConfig(pluginName);

            var jsonValue = pluginConfig.GetAttributeValue<string>("defaultvalue");
            if (string.IsNullOrEmpty(jsonValue))
            {
                HandleExecute();
                return;
            }

            var operations = GetOperations(jsonValue);
            var className = this.GetType().Name;

            var valid = operations.Any(o => o.Name == className && o.Enabled);
            if (!valid) return;

            HandleExecute();
        }

        private EnvironmentOperationModel[] GetOperations(string jsonValue)
        {
            var result = Deserialize<EnvironmentDefinitionModel>(jsonValue);
            return result.Operations;
        }

        public static T Deserialize<T>(string jsonObject)
        {
            using (var stream = new MemoryStream())
            {
                var serializer = new DataContractJsonSerializer(typeof(T));
                var writer = new StreamWriter(stream);
                writer.Write(jsonObject);
                writer.Flush();
                stream.Position = 0;

                T result = (T)serializer.ReadObject(stream);
                return result;
            }
        }

        private Entity GetPluginConfig(string pluginName)
        {
            var query = new QueryExpression("environmentvariabledefinition")
            {
                ColumnSet = new ColumnSet("defaultvalue")
            };
            query.Criteria.AddCondition("schemaname", ConditionOperator.Equal, pluginName);

            var result = AdminService.RetrieveMultiple(query);

            return result.Entities.Any() ? result.Entities[0] : new Entity();
        }

        public abstract void HandleExecute();
    }
}

From the above code, you will learn that we are creating the Interface name IOperation. From there, we create an abstract class Operation. In this class, we handle how to read the configuration to determine if the business logic should be run or not. To get the Entity Configuration, we will filter based on the plugin name. Below is the sample of the Entity Configuration + Default Json Value (in the right hand):

Sample Config + JSON

Then to know the business logic name, we will use this.GetType().Name and filter it. If the business logic exists + enabled, then we will continue to run the HandleExecute function. Else we will skip it.

Plugin + Plugin Step

Once the framework is done, then we will demo-ing some samples of the business logic. To keep it simple, I’ll just put a trace on 3 business logic (A, B, and C). Then at the end of the operation, I’ll throw an error so we are able to see it in the error detail:

using Microsoft.Xrm.Sdk;

namespace DemoPlugin
{
    public class Plugin1 : PluginBase
    {
        public Plugin1(string unsecureConfiguration, string secureConfiguration)
            : base(typeof(Plugin1))
        { }

        // Entry point for custom business logic execution
        protected override void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
        {
            new OperationA(localPluginContext).Execute();
            new OperationB(localPluginContext).Execute();
            new OperationC(localPluginContext).Execute();

            throw new InvalidPluginExecutionException("Marry xmas!");
        }
    }

    public class OperationA : Operation
    {
        public OperationA(ILocalPluginContext localPluginContext) : base(localPluginContext)
        {
        }

        public override void HandleExecute()
        {
            LocalPluginContext.TracingService.Trace("Run OperationA");
        }
    }

    public class OperationB : Operation
    {
        public OperationB(ILocalPluginContext localPluginContext) : base(localPluginContext)
        {
        }

        public override void HandleExecute()
        {
            LocalPluginContext.TracingService.Trace("Run OperationB");
        }
    }

    public class OperationC : Operation
    {
        public OperationC(ILocalPluginContext localPluginContext) : base(localPluginContext)
        {
        }

        public override void HandleExecute()
        {
            LocalPluginContext.TracingService.Trace("Run OperationC");
        }
    }
}

Here is how I register the plugin step (I put it in the Create message):

Plugin Step

Demonstration

Demo time!

Happy CRM-ing!

One thought on “Dynamics CRM: Implement Feature Flags in Plugin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.