如何在运行时从 Web 服务内部获取 CloudConfiguration 的完整列表?

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

ConfigurationManager 具有 AppSettings 名称-值集合,但 CloudConfigurationManager 只有 GetSetting(string) 方法,如果您知道密钥,您可以在其中一对一地获取配置设置。

有没有办法获取角色运行时的整个配置?

根本原因是我想进行强类型配置,以便将其抽象化并使我的代码更易于测试。直接使用 CloudConfigurationManager 是隐式依赖,我想用一个我想在测试中存根的抽象来删除它。所以我觉得这很实用。这让我想到了我的问题。

我不想使用像 fx.configuration.azure 这样的库,因为我将不得不完全携带它的依赖关系,因为它需要继承基础 class.

据我所知,没有可用的直接方法可以为您提供此信息。

不过,您可以使用一种解决方法。它涉及使用服务管理 API 的 Get Deployment 操作。此操作将 return 一个 XML 并且其中一个元素是 Configuration,其中包含 Base64 编码格式的服务配置文件。您可以读取此元素,将其转换为字符串并解析 XML 以获取 ConfigurationSettings 元素。它的子元素包含所有设置。

为此,您可以在服务管理 REST API 上编写自己的包装器或使用 Azure Management Library

更新

下面是一个示例代码,用于使用 Azure Management Library 列出 Service Configuration File 中的所有配置设置。这是一个简单的控制台应用程序,可以在很短的时间内组合在一起,因此有很大的改进空间:)。对于管理证书,我使用了发布设置文件中的数据。

您只需在控制台应用程序中安装 Azure Management Library Nuget 包:

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();
        }
    }
}

这是一个更新的实现,它会注意您是否在模拟器中 运行 以及您是否在本地 Web 服务器中 运行。返回字典后,可以通过 Castle.DictionaryAdapter 轻松地将其从整个应用程序中抽象出来。我在 GitHub here 上将代码作为模板项目共享。以下是摘录:

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;
        }

    }
}