How to use Environment Variables in your plugins

by Sep 5, 2022

Environment Variables

Dynamics 365 / Power Apps solution can have Environment Variables. Often a Settings table (=entity) would be created to store configuration settings that differ between environments. We now can replace these with environment variables.

These environment variables can be added to a solution, be exported, and imported in another environment. If you do it correctly it even asks for the values for the environment variables when you import.

They can have distinct types, but the simplest one is Text. Other options are Decimal number, Two options and JSON. JSON is the most flexible one because you can store multiple settings, so you don’t need to create an environment variable for each one. It is also future proof because you can add more settings later.

There are also two more advanced data types, Data source and Secret (preview) which make it possible to store the values outside of Dataverse. With Data source you need to add a connector and Secret values are stored in Azure Key Vault.

The variables are stored in the tables environmentvariabledefinition and environmentvariablevalue, which can be secured using Dataverse Users and Roles.

You can use these environment variables in Client-side code (JavaScript), Plugins and in Power Automate but there is no out-of-the-box way to retrieve them. You need to query the tables to get the values.

new environment variable

Retrieving Environment Variables

So, there is not a straightforward way to retrieve them in plugins. Therefore, I added a method to my base plugin class to get the variables in a dictionary. You can also add this code in your specific plugin, instead of your base plugin.

I have added the methode GetEnvironmentVariables which will retrieve all the environment variables in the environment with the Current Values. It will pick the first Current Value if there are more than one. If there are no Current Values, then it will retrieve the Default Value. I don’t check for the status (active/Inactive), but this can be added easily if needed.

It does this by querying the table environmentvariabledefinition, which contains the name, type and default value. The current values are stored in a separated table named environmentvariablevalue. The code assumes the values are of the data type Text. I haven’t tested this code with other data types yet, but I assume JSON will also work.

The environment variables are retrieved on every execution of a plugin, so I have added a temporary cache which helps when the plugin is run multiple times under high load.

public class Plugin : IPlugin
{
    static IReadOnlyDictionary<string, string> EnvVariables;
    static readonly object Lock = new();
    
    protected static IReadOnlyDictionary<string, string> GetEnvironmentVariables(LocalPluginContext ctx)
    {
        ctx.Trace($"Entered GetEnvironmentVariables");
            
        // Singleton pattern to load environment variables less
        if (EnvVariables == null)
        {
            lock (Lock)
            {
                if (EnvVariables == null)
                {
                    ctx.Trace($"Load environment variables");
                    var envVariables = new Dictionary<string, string>();

                    var query = new QueryExpression("environmentvariabledefinition")
                    {
                        ColumnSet = new ColumnSet("statecode", "defaultvalue", "valueschema",
                          "schemaname", "environmentvariabledefinitionid", "type"),
                        LinkEntities =
                        {
                            new LinkEntity
                            {
                                JoinOperator = JoinOperator.LeftOuter,
                                LinkFromEntityName = "environmentvariabledefinition",
                                LinkFromAttributeName = "environmentvariabledefinitionid",
                                LinkToEntityName = "environmentvariablevalue",
                                LinkToAttributeName = "environmentvariabledefinitionid",
                                Columns = new ColumnSet("statecode", "value", "environmentvariablevalueid"),
                                EntityAlias = "v"
                            }
                        }
                    };

                    var results = ctx.SystemUserService.RetrieveMultiple(query);
                    if (results?.Entities.Count > 0)
                    {
                        foreach (var entity in results.Entities)
                        {
                            var schemaName = entity.GetAttributeValue<string>("schemaname");
                            var value = entity.GetAttributeValue<AliasedValue>("v.value")?.Value?.ToString();
                            var defaultValue = entity.GetAttributeValue<string>("defaultvalue");

                            ctx.Trace($"- schemaName:{schemaName}, value:{value}, defaultValue:{defaultValue}");
                            if (schemaName != null && !envVariables.ContainsKey(schemaName))
                                envVariables.Add(schemaName, string.IsNullOrEmpty(value) ? defaultValue : value);
                        }
                    }

                    EnvVariables = envVariables;
                }
            }
        }

        ctx.Trace($"Exiting GetEnvironmentVariables");
        return EnvVariables;
    }

    // Other code of the base Plugin...
}

How to use it in your plugins?

If you added the code above to your base plugin (or your specific plugin), you should be able to use the method GetEnvironmentVariables which will return a Dictionary with all the environment variables and their values in the environment.

You can then use the Dictionary to lookup the variable you need. See the example below:

ctx.Trace($"Try getting av_myvariable from environment variables");
if (!GetEnvironmentVariables(ctx).TryGetValue("av_myvariable", out string myVariable))
        throw new Exception("Couldn't read environmentvariable av_myvariable!");

Remy van Duijkeren

Remy van Duijkeren

Power Platform Advisor

Microsoft Power Platform Advisor with over 25 years of experience in IT with a focus on (marketing) automation and integration.

Helping organizations with scaling their business by automating processes on the Power Platform (Dynamics 365).

Expert in Power Platform, Dynamics 365 (Marketing & Sales) and Azure Integration Services. Giving advice and strategy consultancy.

Services:
– Strategy and tactics advise
– Automating and integrating

Subscribe to
The Daily Friction

A daily newsletter on automation and eliminating friction

Related Content

External authentication with Dataverse ServiceClient

For a while now we can use the new Dataverse ServiceClient that replaced the old CrmServiceClient. It has three big improvements: Works for .NET 5.0 and up (.NET Core) Uses the newer MSAL.NET instead of ADAL.NET (which is out of support) for authentication Support for...

read more

Everyone got ALM wrong in Dynamics 365 / Dataverse

For ages, we've been ferociously encouraging the integration of developer practices, such as source control and ALM, into the Dynamics 365/Dataverse realm. The ultimate truth The revered 'Master Branch' in source control, has always been the sole fountainhead from...

read more
Early-Bound Classes for .NET 4.6.2 and 6.0

Early-Bound Classes for .NET 4.6.2 and 6.0

You like to use strong types in .NET when working with Dataverse / Dynamics 365? Are you into Early-Bound Classes? Generating entity classes? You can use CrmSvcUtil for this, but I personally like to use XrmContext from Delegate to do this, because it creates smaller...

read more