How to get full list of CloudConfiguration from inside a web service at runtime?

437 views Asked by At

ConfigurationManager has AppSettings name-value collection but CloudConfigurationManager has only GetSetting(string) method where you can get the config settings 1 by 1 if you know the key.

Is there a way to get the whole config of the role runtime?

The root cause is that I want to make strong typed configuration in order to abstract it away and make my code more testable. Using CloudConfigurationManager directly is implicit dependency which I want to remove with an abstraction which I want to stub in tests. So I find this practical. Which brings me to my question.

I do not want to use library like fx.configuration.azure because I will have to carry its dependency altogether because it requires inheritance of a base class.

2

There are 2 answers

2
Gaurav Mantri On BEST ANSWER

AFAIK, there's no direct method available which will give you this information.

However there's a workaround that you can use. It involves making use of Service Management API's Get Deployment operation. This operation will return an XML and one of the element there is Configuration which contains your service configuration file in Base64 encoded format. You can read this element, convert it into string and parse the XML to get to ConfigurationSettings elements. It's child elements contains all the settings.

For this, you could either write your own wrapper over Service Management REST API or make use of Azure Management Library.

UPDATE

So here's a sample code for listing all configuration settings from Service Configuration File using Azure Management Library. It's a simple console app hacked together in very short amount of time thus has a lot of scope of improvement :). For management certificate, I have used the data from Publish Setting File.

You just have to install Azure Management Library Nuget Package in your console application:

Install-Package Microsoft.WindowsAzure.Management.Libraries

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Management.Compute;
using System.Security.Cryptography.X509Certificates;
using System.Xml.Linq;

namespace ReadConfigurationSettingsUsingAzureManagementLibrary
{
    class Program
    {
        static string subscriptionId = "<subscription-id>";
        static string managementCertContents = "<Base64 Encoded Management Certificate String from Publish Setting File>";//Certificate string from Azure Publish Settings file
        static string cloudServiceName = "<your cloud service name>";
        static string ns = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration";
        static void Main(string[] args)
        {
            var managementCetificate = new X509Certificate2(Convert.FromBase64String(managementCertContents));
            var credentials = new CertificateCloudCredentials(subscriptionId, managementCetificate);

            var computeManagementClient = new ComputeManagementClient(credentials);
            var response = computeManagementClient.HostedServices.GetDetailed(cloudServiceName);
            var deployment = response.Deployments.FirstOrDefault(d => d.DeploymentSlot == Microsoft.WindowsAzure.Management.Compute.Models.DeploymentSlot.Production);
            if (deployment != null)
            {
                var config = deployment.Configuration;
                XElement configXml = XElement.Parse(config);
                var roles = configXml.Descendants(XName.Get("Role", ns));
                foreach (var role in roles)
                {
                    Console.WriteLine(role.Attribute("name").Value);
                    Console.WriteLine("-----------------------------");
                    var configurationSettings = role.Element(XName.Get("ConfigurationSettings", ns));
                    foreach (var element in configurationSettings.Elements(XName.Get("Setting", ns)))
                    {
                        var settingName = element.Attribute("name").Value;
                        var settingValue = element.Attribute("value").Value;
                        Console.WriteLine(string.Format("{0} = {1}", settingName, settingValue));
                    }
                    Console.WriteLine("==========================================");
                }
            }
            Console.ReadLine();
        }
    }
}
0
Ognyan Dimitrov On

Here is an updated implementation which takes care if you are running in emulator or not and if you are running in local web server or not. After returning the dictionary it can be easily abstracted away from the whole application by Castle.DictionaryAdapter. I shared the code as template project on GitHub here. Here is an excerpt:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Xml.Linq;
using Castle.Components.DictionaryAdapter;
using Core.Configuration.Interfaces;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Management.Compute;
using Microsoft.WindowsAzure.Management.Compute.Models;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace Core.Configuration
{
    public class AzureServiceConfigurationProvider : IAzureServiceConfigurationProvider
    {
        private readonly string _subscriptionId;
        // The Base64 Encoded Management Certificate string from Azure Publish Settings file 
        // download from https://manage.windowsazure.com/publishsettings/index
        private readonly string _managementCertContents;
        private readonly string _cloudServiceName;
        private readonly string _serviceConfigurationNamespace;

        public DefaultAzureServiceConfigurationProvider(IWebConfigSettings webConfigSettings)
        {
            _subscriptionId = webConfigSettings.SubscriptionId;
            _managementCertContents = webConfigSettings.ManagementCertContents;
            _cloudServiceName = webConfigSettings.CloudServiceName;
            _serviceConfigurationNamespace = webConfigSettings.ServiceConfigurationNamespace;
        }

        public Dictionary<string, Dictionary<string, string>> GetConfigRaw()
        {
            Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigRaw->Start");
            var configuration = new Dictionary<string, Dictionary<string, string>>();
            var configXml = GetConfigXml();
            Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigRaw->XmlExtracted");

            var roles = configXml.Descendants(XName.Get("Role", _serviceConfigurationNamespace));
            Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigRaw->Roles : ");
            foreach(var role in roles)
            {
                var roleConfiguration = new Dictionary<string, string>();
                var roleName = role.Attribute("name").Value;
                Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigRaw->RoleName : " + roleName);
                var configurationSettings = role.Element(XName.Get("ConfigurationSettings", _serviceConfigurationNamespace));

                if (configurationSettings == null)
                {
                    throw new InvalidOperationException("configurationSettings is null");
                }

                foreach(var element in configurationSettings.Elements(XName.Get("Setting", _serviceConfigurationNamespace)))
                {

                    var settingName = element.Attribute("name").Value;
                    var settingValue = element.Attribute("value").Value;
                    Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigRaw->settingName : " + settingName + " settingValue : " + settingValue);
                    roleConfiguration.Add(settingName, settingValue);
                }
                configuration.Add(roleName, roleConfiguration);
            }
            return configuration;
        }

        public IAzureServiceConfiguration GetConfig()
        {
            var configFactory = new DictionaryAdapterFactory();
            IAzureServiceConfiguration config;
            try
            {

                var rawAzureServiceConfig = GetConfigRaw();
                Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfig :");
                var rawAzureWebServiceConfig = rawAzureServiceConfig["Core.Web"];
                config = configFactory.GetAdapter<IAzureServiceConfiguration>(rawAzureWebServiceConfig);
                config = ComplementConfigurationFromConfigurationManager(config);
            }
            catch(Exception exception)
            {
                // happens in some projects when using Full Emulator
                // so we fallback to cloudconfigurationmanager
                // this is not bad since we have isolated it in configuration assembly

                Trace.WriteLine(exception.Message);
                Trace.WriteLine(exception.StackTrace);
                Hashtable hashConfig = GetConfigFromConfigurationManager();
                config = configFactory.GetAdapter<IAzureServiceConfiguration>(hashConfig);
            }

            return config;
        }

        private IAzureServiceConfiguration ComplementConfigurationFromConfigurationManager(IAzureServiceConfiguration config)
        {
            Trace.WriteLine("Complementing configuration");
            var azureConfigType = config.GetType();
            foreach(PropertyInfo property in config.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
            {
                var xmlConfigValue = CloudConfigurationManager.GetSetting(property.Name);

                var liveConfigPropValue = (string)azureConfigType.GetProperty(property.Name).GetValue(config, null);

                if(string.IsNullOrEmpty(liveConfigPropValue))
                {
                    Trace.WriteLine(property.Name + " in live config is empty. Complementing with '" + xmlConfigValue + "' from ConfigurationManager.");
                    property.SetValue(config, xmlConfigValue);
                }
                // do something with the property
            }

            return config;
        }



        private Hashtable GetConfigFromConfigurationManager()
        {
            Hashtable hashConfig = new Hashtable();
            var configProperties = typeof(IAzureServiceConfiguration).GetProperties();

            foreach(PropertyInfo prop in configProperties)
            {
                hashConfig.Add(prop.Name, CloudConfigurationManager.GetSetting(prop.Name));
            }
            return hashConfig;
        }

        private XElement GetConfigXml()
        {
            XElement configXml = null;
            Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigXml");
            if(!RoleEnvironment.IsAvailable/*as local web project*/ || RoleEnvironment.IsEmulated /*as azure emulator project*/)
            {
                Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigXml->!RoleEnvironment.IsAvailable || RoleEnvironment.IsEmulated");
                try
                {
                    var localConfigFile =
                        new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.EnumerateFiles(
                            "*Local.cscfg", SearchOption.AllDirectories).FirstOrDefault();
                    XmlDocument doc = new XmlDocument();
                    doc.Load(localConfigFile.FullName);
                    configXml = XElement.Parse(doc.InnerXml);
                }
                catch(Exception exception) // happens in some projects when using Full Emulator
                {
                    Trace.WriteLine(exception.Message);
                    Trace.WriteLine(exception.StackTrace);
                    throw; // intended - just marking - will catch it above
                }
            }
            else
            {
                Trace.WriteLine("DefaultAzureServiceConfigurationProvider->GetConfigXml->RoleEnvironment ->in cloud");
                var managementCertificate = new X509Certificate2(Convert.FromBase64String(_managementCertContents));
                var credentials = new CertificateCloudCredentials(_subscriptionId, managementCertificate);

                var computeManagementClient = new ComputeManagementClient(credentials);
                var response = computeManagementClient.HostedServices.GetDetailed(_cloudServiceName);
                var deployment = response.Deployments.FirstOrDefault(d => d.DeploymentSlot == DeploymentSlot.Production);
                if(deployment != null)
                {
                    var config = deployment.Configuration;
                    configXml = XElement.Parse(config);
                }
            }
            return configXml;
        }


    }

    internal static class TypeHelpers
    {
        public static bool IsNumber(this object value)
        {
            return value is sbyte
                    || value is byte
                    || value is short
                    || value is ushort
                    || value is int
                    || value is uint
                    || value is long
                    || value is ulong
                    || value is float
                    || value is double
                    || value is decimal;
        }

        public static bool IsString(this object value)
        {
            return value is string;
        }

    }
}