У меня есть приложение .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, который затем подскажет мне, что настройки изменились, поэтому я принимаю изменения и сохраняю настройки. настройки и должны быть восстановлены, чтобы использовать правильные настройки.
Есть ли лучший/рекомендуемый/предпочтительный метод для достижения подобного эффекта? Или, в равной степени, я подошел к этому совершенно неправильно и есть лучший подход?
Любая конфигурация, которая может отличаться в разных средах, должна храниться на уровне машины, а не уровне приложения. (Дополнительная информация о уровнях конфигурации.)
Это типы элементов конфигурации, которые я обычно храню на уровне машины:
Когда каждая среда (разработчик, интеграция, тест, сцена, живая) имеет свои уникальные настройки в каталоге c:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG, тогда вы можете продвигать свой код приложения между средами без каких-либо изменений после сборки.
И, очевидно, содержимое каталога CONFIG на уровне машины получает управление версиями в другом репозитории или другой структуре папок из вашего приложения. Вы можете сделать ваши файлы .config более удобными для управления источниками с помощью интеллектуального использования configSource.
Я занимаюсь этим 7 лет, на более чем 200 приложениях ASP.NET в 25 + разных компаниях. (Не пытаясь похвастаться, просто хочу сообщить, что я никогда не видел ситуации, когда такой подход не работает.)
Это может помочь некоторым людям, имеющим дело с параметрами Settings.settings и App.config: следить за атрибутом GenerateDefaultValueInCode на панели "Свойства" при редактировании любых значений в сетке Settings.settings в Visual Studio (Visual Studio 2008 в моем случае).
Если для параметра GenerateDefaultValueInCode установлено значение True (True здесь по умолчанию!), значение по умолчанию компилируется в EXE (или DLL), вы можете найти его вложенным в файл при его открытии в текстовом редакторе.
Я работал над консольным приложением, и если бы у меня были настройки по умолчанию в EXE, приложение всегда игнорировало место файла конфигурации в том же каталоге! Довольно кошмар и нет информации об этом во всем Интернете.
Здесь есть связанный с этим вопрос:
Конфигурационные файлы имеют способ переопределить настройки:
<appSettings file="Local.config">
Вместо того, чтобы проверять два файла (или больше), вы проверяете только файл конфигурации по умолчанию, а затем на каждом целевом компьютере вы помещаете файл Local.config с помощью только секции appSettings, которая имеет переопределения для этой конкретной машины.
Если вы используете разделы конфигурации, эквивалент:
configSource="Local.config"
Конечно, рекомендуется создавать резервные копии всех файлов Local.config с других компьютеров и проверять их где-то, но не как часть реальных решений. Каждый разработчик помещает "игнорировать" в файл Local.config, чтобы он не попадал в него, что бы перезаписать все остальные файлы.
(На самом деле вам не нужно называть его "Local.config", это то, что я использую)
Из того, что я читаю, похоже, что вы используете 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>
Мне кажется, что вы можете воспользоваться проектом Visual Studio 2005 для веб-развертывания.
С этим вы можете сообщить об этом, чтобы обновить/изменить разделы вашего файла web.config в зависимости от конфигурации сборки.
Взгляните на эту запись в блоге от Scott Gu для быстрого обзора/примера.
Раньше мы использовали проекты веб-развертывания, но с тех пор перенаправлялись в NAnt. Вместо того, чтобы разворачивать и копировать разные файлы настроек, мы вставляем значения конфигурации непосредственно в сборку script и внедряем их в наши файлы конфигурации с помощью задач xmlpoke:
<xmlpoke
file="${stagingTarget}/web.config"
xpath="/configuration/system.web/compilation/@debug"
value="true"
/>
В любом случае ваши файлы конфигурации могут обладать любыми необходимыми значениями разработчика, и они отлично справятся с вашей средой разработки без нарушения ваших производственных систем. Мы обнаружили, что разработчики с меньшей вероятностью произвольно изменяют переменные build script при тестировании, поэтому случайные неправильные конфигурации были реже, чем с другими методами, которые мы пробовали, хотя по-прежнему необходимо добавить каждый var в начале процесса так что значение dev не может быть нажато на prod по умолчанию.
Мой нынешний работодатель решил эту проблему, предварительно поставив уровень dev (debug, stage, live и т.д.) в файл machine.config. Затем они написали код, чтобы выбрать его и использовать правильный файл конфигурации. Это решило проблему с неправильной строкой соединения после развертывания приложения.
Они только недавно написали центральный веб-сервис, который отправляет правильную строку соединения из значения в значение machine.config.
Это лучшее решение? Наверное, нет, но это работает для них.
Одним из решений, которые отлично меня работали, является использование 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>
Наш 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. вы строите. Обратите внимание, что вы должны работать с копией файла конфигурации, а не с самим файлом конфигурации, чтобы избежать возможных ошибок, которые невозможно устранить.
Просто запустите файл сборки и переместите обновленный файл конфигурации в свою среду развертывания, и все готово!
Для лучшего обзора прочтите следующее:
Здесь вы найдете другое решение: Лучший способ переключения конфигурации между средами разработки /UAT/Prod в ASP.NET?, который использует XSLT для трансфорнирования в Интернете. конфигурации.
Также есть несколько примеров использования NAnt.
Как и я, я также установил '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)
В нем указано 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
}
}