Конфигурация .NET (app.config / web.config / settings.settings)

159

У меня есть приложение .NET, которое имеет разные файлы конфигурации для сборки Debug и Release. Например. файл debug app.config указывает на развитие SQL Server, в котором включена отладка, а цель релиза - на Live SQL Server. Существуют и другие настройки, некоторые из которых отличаются от debug/release.

В настоящее время я использую два отдельных файла конфигурации (debug.app.config и release.app.config). У меня есть событие сборки проекта, в котором говорится, что если это сборка релиза, тогда скопируйте файл release.app.config в app.config, иначе скопируйте файл debug.app.config в app.config.

Проблема заключается в том, что приложение, похоже, получает свои настройки из файла settings.settings, поэтому мне нужно открыть settings.settings в Visual Studio, который затем подскажет мне, что настройки изменились, поэтому я принимаю изменения и сохраняю настройки. настройки и должны быть восстановлены, чтобы использовать правильные настройки.

Есть ли лучший/рекомендуемый/предпочтительный метод для достижения подобного эффекта? Или, в равной степени, я подошел к этому совершенно неправильно и есть лучший подход?

Теги:
configuration

12 ответов

61

Любая конфигурация, которая может отличаться в разных средах, должна храниться на уровне машины, а не уровне приложения. (Дополнительная информация о уровнях конфигурации.)

Это типы элементов конфигурации, которые я обычно храню на уровне машины:

Когда каждая среда (разработчик, интеграция, тест, сцена, живая) имеет свои уникальные настройки в каталоге c:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG, тогда вы можете продвигать свой код приложения между средами без каких-либо изменений после сборки.

И, очевидно, содержимое каталога CONFIG на уровне машины получает управление версиями в другом репозитории или другой структуре папок из вашего приложения. Вы можете сделать ваши файлы .config более удобными для управления источниками с помощью интеллектуального использования configSource.

Я занимаюсь этим 7 лет, на более чем 200 приложениях ASP.NET в 25 + разных компаниях. (Не пытаясь похвастаться, просто хочу сообщить, что я никогда не видел ситуации, когда такой подход не работает.)

  • 3
    Как насчет ситуации, когда вы не контролируете веб-сервер и, следовательно, не можете изменить конфигурацию на уровне машины? Примеры включают сторонний веб-сервер или веб-сервер, совместно используемый несколькими подразделениями на предприятии.
  • 1
    Не будет работать Но в эпоху виртуальных машин, Amazon EC2 и 400-долларовых серверов от Dell, кто-нибудь действительно делает что-нибудь серьезное на общих машинах? Не пытаясь быть черствым - я действительно думаю, что если вы работаете на общем веб-сервере, вам следует пересмотреть.
Показать ещё 4 комментария
51

Это может помочь некоторым людям, имеющим дело с параметрами Settings.settings и App.config: следить за атрибутом GenerateDefaultValueInCode на панели "Свойства" при редактировании любых значений в сетке Settings.settings в Visual Studio (Visual Studio 2008 в моем случае).

Если для параметра GenerateDefaultValueInCode установлено значение True (True здесь по умолчанию!), значение по умолчанию компилируется в EXE (или DLL), вы можете найти его вложенным в файл при его открытии в текстовом редакторе.

Я работал над консольным приложением, и если бы у меня были настройки по умолчанию в EXE, приложение всегда игнорировало место файла конфигурации в том же каталоге! Довольно кошмар и нет информации об этом во всем Интернете.

  • 7
    Это именно то, что случилось со мной за прошедшие выходные. Я вырвал много волос, пытаясь понять, почему мое приложение, похоже, игнорирует мой файл app.config! Предполагается подключиться к веб-службе, а URL-адрес службы находится в моем app.config. Без моего ведома, когда я создал веб-ссылку, он также создал файл Settings.Settings И жестко запрограммировал значение по умолчанию в коде. Даже когда я наконец выяснил (и удалил) файл настроек, это значение по умолчанию осталось в жестком коде и было встроено в исполняемый файл. ОЧЕНЬ БЕСПОКОЙНО !! Благодаря этому посту, теперь я могу избавиться от этой "функции"
  • 0
    +1 Этот ответ является критическим : если вы хотите, чтобы ваши настройки помещались в файл app.config, установите для его атрибута GenerateDefaultValueInCode значение False (по умолчанию установлено значение True).
31

Здесь есть связанный с этим вопрос:

Улучшение процесса сборки

Конфигурационные файлы имеют способ переопределить настройки:

<appSettings file="Local.config">

Вместо того, чтобы проверять два файла (или больше), вы проверяете только файл конфигурации по умолчанию, а затем на каждом целевом компьютере вы помещаете файл Local.config с помощью только секции appSettings, которая имеет переопределения для этой конкретной машины.

Если вы используете разделы конфигурации, эквивалент:

configSource="Local.config"

Конечно, рекомендуется создавать резервные копии всех файлов Local.config с других компьютеров и проверять их где-то, но не как часть реальных решений. Каждый разработчик помещает "игнорировать" в файл Local.config, чтобы он не попадал в него, что бы перезаписать все остальные файлы.

(На самом деле вам не нужно называть его "Local.config", это то, что я использую)

14

Из того, что я читаю, похоже, что вы используете Visual Studio для своего процесса сборки. Вы думали об использовании MSBuild и Nant вместо этого?

Синтаксис Nant xml немного странный, но как только вы это понимаете, выполнение того, что вы упомянули, становится довольно тривиальным.

<target name="build">
    <property name="config.type" value="Release" />

    <msbuild project="${filename}" target="Build" verbose="true" failonerror="true">
        <property name="Configuration" value="${config.type}" />
    </msbuild>

    <if test="${config.type == 'Debug'}">
        <copy file=${debug.app.config}" tofile="${app.config}" />
    </if>

    <if test="${config.type == 'Release'}">
        <copy file=${release.app.config}" tofile="${app.config}" />
    </if>

</target>
11

Мне кажется, что вы можете воспользоваться проектом Visual Studio 2005 для веб-развертывания.

С этим вы можете сообщить об этом, чтобы обновить/изменить разделы вашего файла web.config в зависимости от конфигурации сборки.

Взгляните на эту запись в блоге от Scott Gu для быстрого обзора/примера.

8

Раньше мы использовали проекты веб-развертывания, но с тех пор перенаправлялись в NAnt. Вместо того, чтобы разворачивать и копировать разные файлы настроек, мы вставляем значения конфигурации непосредственно в сборку script и внедряем их в наши файлы конфигурации с помощью задач xmlpoke:

  <xmlpoke
    file="${stagingTarget}/web.config"
    xpath="/configuration/system.web/compilation/@debug"
    value="true"
  />

В любом случае ваши файлы конфигурации могут обладать любыми необходимыми значениями разработчика, и они отлично справятся с вашей средой разработки без нарушения ваших производственных систем. Мы обнаружили, что разработчики с меньшей вероятностью произвольно изменяют переменные build script при тестировании, поэтому случайные неправильные конфигурации были реже, чем с другими методами, которые мы пробовали, хотя по-прежнему необходимо добавить каждый var в начале процесса так что значение dev не может быть нажато на prod по умолчанию.

7

Мой нынешний работодатель решил эту проблему, предварительно поставив уровень dev (debug, stage, live и т.д.) в файл machine.config. Затем они написали код, чтобы выбрать его и использовать правильный файл конфигурации. Это решило проблему с неправильной строкой соединения после развертывания приложения.

Они только недавно написали центральный веб-сервис, который отправляет правильную строку соединения из значения в значение machine.config.

Это лучшее решение? Наверное, нет, но это работает для них.

  • 1
    На самом деле я думаю, что это чертовски элегантно, так как мне нравится держать различные версии конфигурации видимыми внутри решения, даже если они не работают.
  • 1
    Это очень интригующее решение. Хотелось бы рассмотреть пример этого в действии.
5

Одним из решений, которые отлично меня работали, является использование WebDeploymentProject. У меня было 2/3 различных файла web.config на моем сайте и в публикации, в зависимости от выбранного режима конфигурации (release/постановка/etc...) я бы скопировал через Web.Release.config и переименовал его в веб. config в событии AfterBuild и удалить те, которые мне не нужны (например, Web.Staging.config).

<Target Name="AfterBuild">
    <!--Web.config -->
    <Copy Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Release.config" DestinationFiles="$(OutputPath)\Web.config" />
    <Copy Condition=" '$(Configuration)|$(Platform)' == 'Staging|AnyCPU' " SourceFiles="$(SourceWebPhysicalPath)\Web.Staging.config" DestinationFiles="$(OutputPath)\Web.config" />
    <!--Delete extra files -->
    <Delete Files="$(OutputPath)\Web.Release.config" />
    <Delete Files="$(OutputPath)\Web.Staging.config" />
    <Delete Files="@(ProjFiles)" />
  </Target>
3

Наш proj имеет ту же проблему, что и мы должны поддерживать конфиги для dev, qa, uat и prod. Вот что мы следовали (применяется только в том случае, если вы знакомы с MSBuild):

Используйте MSBuild с расширением задач сообщества MSBuild. Он включает задачу "XmlMassUpdate", которая может "массово обновлять" записи в любом XML файле, как только вы дадите ему правильный node, чтобы начать с.

Для реализации:

1) У вас должен быть один файл конфигурации, который будет содержать ваши записи env env; это файл конфигурации в вашем решении.

2) Для каждого окружения необходимо иметь файл "Substitutions.xml", который содержит только те записи, которые являются РАЗЛИЧНЫМИ (главным образом, приложения и привязки). Записи, которые не изменяются в разных средах, не нужно помещать в этот файл. Они могут жить в файле web.config решения и не будут затронуты задачей

3) В вашем файле сборки просто вызовите задачу массового обновления XML и укажите правильную среду в качестве параметра.

См. пример ниже:

    <!-- Actual Config File -->
    <appSettings>
        <add key="ApplicationName" value="NameInDev"/>
        <add key="ThisDoesNotChange" value="Do not put in substitution file" />
    </appSettings>

    <!-- Substitutions.xml -->
    <configuration xmlns:xmu="urn:msbuildcommunitytasks-xmlmassupdate">
      <substitutions>
        <QA>
           <appSettings>
            <add xmu:key="key" key="ApplicationName" value="NameInQA"/>
           </appSettings>            
        </QA>
        <Prod>
          <appSettings>
            <add xmu:key="key" key="ApplicationName" value="NameInProd"/>
          </appSettings>            
        </Prod>
     </substitutions>
    </configuration>


<!-- Build.xml file-->

    <Target Name="UpdateConfigSections">
            <XmlMassUpdate ContentFile="Path\of\copy\of\latest web.config" SubstitutionsFile="path\of\substitutionFile" ContentRoot="/configuration" SubstitutionsRoot="/configuration/substitutions/$(Environment)" />
        </Target>

заменить "$ Environment" на "QA" или "Prod" на основе того, что env. вы строите. Обратите внимание, что вы должны работать с копией файла конфигурации, а не с самим файлом конфигурации, чтобы избежать возможных ошибок, которые невозможно устранить.

Просто запустите файл сборки и переместите обновленный файл конфигурации в свою среду развертывания, и все готово!

Для лучшего обзора прочтите следующее:

http://blogs.microsoft.co.il/blogs/dorony/archive/2008/01/18/easy-configuration-deployment-with-msbuild-and-the-xmlmassupdate-task.aspx

3

Здесь вы найдете другое решение: Лучший способ переключения конфигурации между средами разработки /UAT/Prod в ASP.NET?, который использует XSLT для трансфорнирования в Интернете. конфигурации.

Также есть несколько примеров использования NAnt.

2

Как и я, я также установил 'multi' app.config - например app.configDEV, app.configTEST, app.config.LOCAL. Я вижу некоторые из превосходных альтернатив, предложенных, но если вам нравится, как это работает для вас, я бы добавил следующее:

У меня есть
  <appSettings>
      <add key = "Env" value = "[Local] "/> для каждого приложения я добавляю это в пользовательский интерфейс в заголовке: из ConfigurationManager.AppSettings.Get( "Env" );

Я просто переименовываю конфигурацию в ту, на которую я нацелен (у меня есть проект с 8 приложениями с большим количеством базы данных /wcf config против 4 равноценных значений). Чтобы развернуть с помощью clickonce в каждом, я меняю 4 места в проекте и иди. (это я бы хотел автоматизировать)

Мое единственное, что нужно, - это помнить, чтобы "очистить все" после изменения, поскольку старая конфигурация "застревает" после ручного переименования. (Которое я думаю, УБЕДИТЕСЬ, что вы установили проблему установки.)

Я нахожу, что это работает очень хорошо (однажды я получу время, чтобы посмотреть MSBuild/NAnt)

0

В нем указано asp.net выше, так почему бы не сохранить ваши настройки в базе данных и использовать пользовательский кеш для их извлечения?

Причина, по которой мы это сделали, заключается в том, что нам проще (для нас) обновлять постоянно базу данных, чем получать разрешение на постоянное обновление производственных файлов.

ПРИМЕР пользовательского кэша:

public enum ConfigurationSection
{
    AppSettings
}

public static class Utility
{
    #region "Common.Configuration.Configurations"

    private static Cache cache = System.Web.HttpRuntime.Cache;

    public static String GetAppSetting(String key)
    {
        return GetConfigurationValue(ConfigurationSection.AppSettings, key);
    }

    public static String GetConfigurationValue(ConfigurationSection section, String key)
    {
        Configurations config = null;

        if (!cache.TryGetItemFromCache<Configurations>(out config))
        {
            config = new Configurations();
            config.List(SNCLavalin.US.Common.Enumerations.ConfigurationSection.AppSettings);
            cache.AddToCache<Configurations>(config, DateTime.Now.AddMinutes(15));
        }

        var result = (from record in config
                      where record.Key == key
                      select record).FirstOrDefault();

        return (result == null) ? null : result.Value;
    }

    #endregion
}

namespace Common.Configuration
{
    public class Configurations : List<Configuration>
    {
        #region CONSTRUCTORS

        public Configurations() : base()
        {
            initialize();
        }
        public Configurations(int capacity) : base(capacity)
        {
            initialize();
        }
        public Configurations(IEnumerable<Configuration> collection) : base(collection)
        {
            initialize();
        }

        #endregion

        #region PROPERTIES & FIELDS

        private Crud _crud; // Db-Access layer

        #endregion

        #region EVENTS
        #endregion

        #region METHODS

        private void initialize()
        {
            _crud = new Crud(Utility.ConnectionName);
        }

        /// <summary>
        /// Lists one-to-many records.
        /// </summary>
        public Configurations List(ConfigurationSection section)
        {
            using (DbCommand dbCommand = _crud.Db.GetStoredProcCommand("spa_LIST_MyConfiguration"))
            {
                _crud.Db.AddInParameter(dbCommand, "@Section", DbType.String, section.ToString());

                _crud.List(dbCommand, PopulateFrom);
            }

            return this;
        }

        public void PopulateFrom(DataTable table)
        {
            this.Clear();

            foreach (DataRow row in table.Rows)
            {
                Configuration instance = new Configuration();
                instance.PopulateFrom(row);
                this.Add(instance);
            }
        }

        #endregion
    }

    public class Configuration
    {
        #region CONSTRUCTORS

        public Configuration()
        {
            initialize();
        }

        #endregion

        #region PROPERTIES & FIELDS

        private Crud _crud;

        public string Section { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }

        #endregion

        #region EVENTS
        #endregion

        #region METHODS

        private void initialize()
        {
            _crud = new Crud(Utility.ConnectionName);
            Clear();
        }

        public void Clear()
        {
            this.Section = "";
            this.Key = "";
            this.Value = "";
        }
        public void PopulateFrom(DataRow row)
        {
            Clear();

            this.Section = row["Section"].ToString();
            this.Key = row["Key"].ToString();
            this.Value = row["Value"].ToString();
        }

        #endregion
    }
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню