Файлы конфигурации и получение в Azure-Functions

Я экспериментировал с файлами .config в Azure-Functions.

Если я напишу эту функцию

using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;


namespace GranadaCoder.AzurePoc.AzureFunctionsOne
{
    public static class AppSettingsTestOne
    {
        [FunctionName("AppSettingsTestOneFunctionName")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {

            try
            {

                string rootDirectory = string.Empty;
                if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HOME")))
                {
                    /* running in azure */
                    rootDirectory = Environment.GetEnvironmentVariable("HOME") + "\\site\\wwwroot";
                }
                else
                {
                    /* in visual studio, local debugging */
                    rootDirectory = ".";
                }
                string path = rootDirectory + @"\CustomConfigFiles\CustomAppSettings.config";

                if (!System.IO.File.Exists(path))
                {
                    throw new System.IO.FileNotFoundException(string.Format("NOT FOUND!!! ('{0}')", path));
                }
                else
                {
                    log.Info(string.Format("File exists='{0}'", path));
                }

                ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = path };
                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

                Configuration fileConfig = ConfigurationManager.OpenExeConfiguration(path); /* does NOT work */
                string val1 = config.AppSettings.Settings["KeyOne"].Value;
                string val2 = config.AppSettings.Settings["KeyTwo"].Value;
                string val3 = config.AppSettings.Settings["KeyThree"].Value;

                string msg = string.Join(",", val1, val2, val3);

                return req.CreateResponse(HttpStatusCode.OK, msg);
            }
            catch (Exception ex)
            {
                string errorMsg = ex.Message; //  ExceptionHelper.GenerateFullFlatMessage(ex);
                log.Error(errorMsg);
                return req.CreateResponse(HttpStatusCode.BadRequest, errorMsg);
            }
        }
    }
}

с этим файлом .config (CustomAppSettings.config)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="KeyOne" value="ValueOne" />
    <add key="KeyTwo" value="ValueTwo" />
    <add key="KeyThree" value="ValueThree" />
  </appSettings>
</configuration>

Он работает так, как ожидалось.

Если я использую эту функцию:

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;


namespace GranadaCoder.AzurePoc.AzureFunctionsOne
{
    public static class NameValuePairAppSettingsTest
    {
        [FunctionName("NameValuePairAppSettingsTestFunctionName")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {

            try
            {
                string rootDirectory = string.Empty;
                if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HOME")))
                {
                    /* running in azure */
                    rootDirectory = Environment.GetEnvironmentVariable("HOME") + "\\site\\wwwroot";
                }
                else
                {
                    /* in visual studio, local debugging */
                    rootDirectory = ".";
                }
                string path = rootDirectory + @"\CustomConfigFiles\NameValuePairSettings.config";


                if (!System.IO.File.Exists(path))
                {
                    throw new System.IO.FileNotFoundException(string.Format("NOT FOUND!!! ('{0}')", path));
                }
                else
                {
                    log.Info(string.Format("file exists='{0}'", path));
                }

                ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = path };
                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

                //NameValueCollection nvc = (NameValueCollection)config.GetSection("myLittleArea"); /* does not work */

                ConfigurationSection myParamsSection = config.GetSection("myLittleArea");
                /* see https://stackoverflow.com/questions/13825323/how-do-i-get-the-values-from-a-configsection-defined-as-namevaluesectionhandler */
                string myParamsSectionRawXml = myParamsSection.SectionInformation.GetRawXml();
                XmlDocument sectionXmlDoc = new XmlDocument();
                sectionXmlDoc.Load(new StringReader(myParamsSectionRawXml));
                NameValueSectionHandler handler = new NameValueSectionHandler();
                NameValueCollection nvc = handler.Create(null, null, sectionXmlDoc.DocumentElement) as NameValueCollection;

                var items = nvc.AllKeys.SelectMany(nvc.GetValues, (k, v) => new { key = k, value = v });
                ////////foreach (var item in items)
                ////////{
                ////////    Console.WriteLine("{0} {1}", item.key, item.value);
                ////////}

                string msg = string.Join(",", items.ToList());

                return req.CreateResponse(HttpStatusCode.OK, msg);
            }
            catch (Exception ex)
            {
                string errorMsg = ex.Message; //  ExceptionHelper.GenerateFullFlatMessage(ex);
                log.Error(errorMsg);
                return req.CreateResponse(HttpStatusCode.BadRequest, errorMsg);
            }
        }
    }
}

с этим файлом .config (NameValuePairSettings.config)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="myLittleArea" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>

  <myLittleArea>
    <add key="color" value="red"/>
    <add key="street" value="main"/>
    <add key="month" value="july"/>
    <add key="candy" value="snickers"/>
  </myLittleArea>

</configuration>

Все работает нормально.

(Барабанная дробь).

Если я создам раздел пользовательской конфигурации.

using System.Configuration;

namespace GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings
{
    public static class MyCustomConfigurationSettingsConfigurationRetriever
    {
        public static readonly string ConfigurationSectionName = "MyCustomConfigurationSettingsConfigurationSectionName";

        /*
        public static MyCustomConfigurationSettingsConfigurationSection GetMyCustomConfigurationSettings()
        {
            MyCustomConfigurationSettingsConfigurationSection returnSection = (MyCustomConfigurationSettingsConfigurationSection)ConfigurationManager.GetSection(ConfigurationSectionName);
            if (returnSection != null)
            {
                return returnSection;
            }

            return null;
        }
        */

        public static MyCustomConfigurationSettingsConfigurationSection GetMyCustomConfigurationSettings(System.Configuration.Configuration cfg)
        {
            MyCustomConfigurationSettingsConfigurationSection returnSection = (MyCustomConfigurationSettingsConfigurationSection)cfg.GetSection(ConfigurationSectionName);
            if (returnSection != null)
            {
                return returnSection;
            }

            return null;
        }
    }
}

а также

using System.Configuration;

namespace GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings
{
    public class MyCustomConfigurationSettingsConfigurationSection : ConfigurationSection
    {
        private const string FavoriteNumberPropertyName = "FavoriteNumber";
        private const string FavoriteColorPropertyName = "FavoriteColor";

        [ConfigurationProperty(FavoriteNumberPropertyName, IsRequired = true, DefaultValue = 100)]
        public int FavoriteNumber
        {
            get
            {
                return (int)this[FavoriteNumberPropertyName];
            }
        }

        [ConfigurationProperty(FavoriteColorPropertyName, IsRequired = true, DefaultValue = ",")]
        public string FavoriteColor
        {
            get
            {
                return (string)this[FavoriteColorPropertyName];
            }
        }
    }
}

и .config (MyCustomConfigurationSettings.config)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name ="MyCustomConfigurationSettingsConfigurationSectionName" type="GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings.MyCustomConfigurationSettingsConfigurationSection, GranadaCoder.AzurePoc.ConfigurationLibrary" />
  </configSections>
  <MyCustomConfigurationSettingsConfigurationSectionName
    FavoriteNumber="333"
    FavoriteColor="Green"
  >
  </MyCustomConfigurationSettingsConfigurationSectionName>
</configuration>

и код функции Azure

using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;

using GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings;



namespace GranadaCoder.AzurePoc.AzureFunctionsOne
{
    public static class CustomConfigurationTest
    {
        [FunctionName("CustomConfigurationTestFunctionName")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            try
            {
                string rootDirectory = string.Empty;
                if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HOME")))
                {
                    /* running in azure */
                    rootDirectory = Environment.GetEnvironmentVariable("HOME") + "\\site\\wwwroot";
                }
                else
                {
                    /* in visual studio, local debugging */
                    rootDirectory = ".";
                }
                string path = rootDirectory + @"\CustomConfigFiles\MyCustomConfigurationSettings.config";

                log.Info(string.Format("CustomConfigurationTestFunctionName HostingEnvironment.ApplicationPhysicalPath='{0}'", System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath));

                if (!System.IO.File.Exists(path))
                {
                    throw new System.IO.FileNotFoundException(string.Format("NOT FOUND!!! ('{0}')", path));
                }
                else
                {
                    log.Info(string.Format("File exists='{0}'", path));
                }

                ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = path };
                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

                MyCustomConfigurationSettingsConfigurationSection customSection = MyCustomConfigurationSettingsConfigurationRetriever.GetMyCustomConfigurationSettings(config);

                string msg = string.Join(",", customSection.FavoriteNumber.ToString(), customSection.FavoriteColor);

                return req.CreateResponse(HttpStatusCode.OK, msg);
            }
            catch (Exception ex)
            {
                string errorMsg = ex.Message; //  ExceptionHelper.GenerateFullFlatMessage(ex);
                log.Error(errorMsg);
                return req.CreateResponse(HttpStatusCode.BadRequest, errorMsg);
            }
        }
    }
}

Вышеупомянутое не работает.

я получаю сообщение об ошибке

«Произошла ошибка при создании обработчика раздела конфигурации для MyCustomConfigurationSettingsConfigurationSectionName: не удалось загрузить файл или сборку GranadaCoder.AzurePoc.ConfigurationLibrary или одну из ее зависимостей. Система не может найти указанный файл. (C:\blah\blah\blah\bin \Debug\net461\CustomConfigFiles\MyCustomConfigurationSettings.config строка 4)"

Файл ЕСТЬ (см. изображение)

Любая идея, почему пользовательская конфигурация не работает?

введите здесь описание изображения


person granadaCoder    schedule 08.09.2017    source источник
comment
Почему вы решили использовать собственную реализацию конфигурации, а не «обычный» подход с использованием ConfigurationManager? При использовании подхода по умолчанию вы можете использовать функциональные возможности Azure и управления релизами (любой другой системы развертывания). Дополнительную информацию о настройке ваших функций можно найти в этом вопросе SO: stackoverflow.com/a/45681977/352640 у вас есть веская причина не использовать его, просто проигнорируйте этот комментарий. Мне все еще любопытно, почему вы хотите реализовать это так.   -  person Jan_V    schedule 09.09.2017


Ответы (2)


После распечатки BaseDirectory текущего домена в функции Azure я обнаружил, что эта функция запускается fun.exe. Он будет искать сборку в папке «AppData\Local\Azure.Functions.Cli\1.0.1\». После копирования «GranadaCoder.AzurePoc.ConfigurationLibrary» в папку функция будет работать нормально.

Код:

string friendlyName = AppDomain.CurrentDomain.FriendlyName;
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;

Выход:

BaseDirectory = "C:\\Users\\myusername\\AppData\\Local\\Azure.Functions.Cli\\1.0.1\\"
FriendlyName = "func.exe"
person Amor    schedule 11.09.2017
comment
Вы разгадали загадку. Мне не нравится, как это может работать... (убивает сценарии развертывания xcopy).......... но теперь это работает. Вы должны поддерживать файлы в актуальном состоянии ...... или вы получите эту ошибку. (следующий комментарий) - person granadaCoder; 11.09.2017
comment
[A]GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings.MyCustomConfigurationSettingsConfigurationSection нельзя привести к [B]GranadaCoder.AzurePoc.ConfigurationLibrary.MyCustomConfigurationSettings.MyCustomConfigurationSettingsConfigurationSection. - person granadaCoder; 11.09.2017
comment
Тип A происходит из «GranadaCoder.AzurePoc.ConfigurationLibrary, версия = 1.0.0.0, культура = нейтральная, PublicKeyToken = null» в контексте «по умолчанию» в расположении «C:\Users\MyUserName\AppData\Local\Azure.Functions.Cli». \1.0.0-beta.99\GranadaCoder.AzurePoc.ConfigurationLibrary.dll». - person granadaCoder; 11.09.2017
comment
Тип B происходит из «GranadaCoder.AzurePoc.ConfigurationLibrary, версия = 1.0.0.0, культура = нейтральная, PublicKeyToken = null» в контексте «LoadFrom» в расположении «C:\MyDeployment\GranadaCoder.AzurePoc.AzureFunctionsOne\bin\Debug\net461». \GranadaCoder.AzurePoc.ConfigurationLibrary.dll». - person granadaCoder; 11.09.2017
comment
Пояснение к моему предыдущему заявлению. Файл .dll, содержащий логику (интерпретацию пользовательской конфигурации), должен находиться в том же каталоге, где находится func.exe. НО сами настройки могут идти боком с функцией .dll. - person granadaCoder; 11.09.2017
comment
Я все еще нахожу интересным, что он не смотрит в каталоге /bin/ развертывания ........ как dotnet делал в течение 17+ лет. - person granadaCoder; 11.09.2017

Функция Azure поддерживает только ограниченную часть app.config. Это позволяет сохранять настройки приложения и подключения в local.settings.json при запуске функции из VS. Он не поддерживает настройки конечной точки WCF в system.serviceModel в этом файле json. У меня была ссылка на библиотеку dll в AzureFunction, которая внутренне вызывала API WCF.

Я обнаружил странную вещь: когда я запускаю функцию Azure, она преобразует конфигурацию json в xml по пути cli (C:\Users\‹‹ имя компьютера >>\AppData\Local\AzureFunctionsTools\Releases\1.6.0\ cli\func.exe.config). Я добавил свою иерархию конфигурации xml (system.serviceModel) в этот файл конфигурации, и он работал нормально, выбирая конечные точки WCF для запуска служб. Хотя есть проблемы с использованием конфигурации log4net, но я умею запускать API.

Надеюсь это поможет.

person Phantom    schedule 20.12.2018