Cтроки подключения в App.config и Web.config

501
04 мая 2017, 11:25

Представим ситуацию: В Visual Studio есть решение, которое состоит из 3 проектов. В каждом из проектов в файле app.config лежит в открытом виде строка подключения базе данных, развернутой в production среде.

Возникла необходимость работать с этим проектом в команде используя VSTS.

Хочу реализовать такой сценарий: Разработчик синхронизирует себе репозиторий с этим решением, и в файлах app.config строки подключения нет, он должен добавить туда строки подключения к своей локальной тестовой базе данных. После отправки кода обратно в репозиторий, в процессе CI и CD в файлы app.config подставляются нужные строки подключения к рабочей базе данных.

Как можно реализовать это?

Answer 1

В каждом из проектов в файле app.config лежит в открытом виде строка подключения базе данных, развернутой в production среде.

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

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

  • Многие CI-серверы умеют хранить такие переменные и инициализировать ими окружение перед запуском вашего приложения. Точно умеют GitLab CI, Jenkins и Travis (ссылки ведут на документацию по фичам).

  • Доступ к просмотру и редактированию секретных переменных при этом будет у пользователей с максимальным уровнем доступа.

  • Если не засветить переменные в логе сборки, то они не будут доступны случайному прохожему. Просто не дублируйте в лог команду для подключения к базе.
  • Нужно будет также настроить, чтобы секретные переменные использовались только при деплое кода из вашей релизной ветки.

Программист будет инициализировать окружение сам, можно написать какой-то скрипт ему в помощь.

Answer 2

Как уже написал @NickVolynkin держать строки подключения к БД в App.config - плохая практика. Можно создать локальный файл конфигурации, например, ConnectStrings.config, в котором хранить строки подключения, и которого не будет в репозитории (для каждого разработчика он свой). Также написать класс-обработчик этой конфигурации, и при создании подключения к БД вызывать методы этого класса.

Answer 3

Бонусом ко всему сказанному. Есть возможность трансформировать app.config (равно как и любой другой файл) при сборке. Такая функциональность есть по умолчанию в проектах WCF сервисов для web.config, но её же можно добавить в другие типы проектов.

Схема следующая: есть некий дефолтный пустой app.config, рядом с ним лежат дополнительные файлы с названиями app.Debug.config, app.Release.config, app.Test.config и так далее, которые содержат инструкции для трансформации исходного app.config. Если предположить, что в CI и CD используются только определённые конфигурации сборки (например, только Release), то можно в app.Release.config держать нужный набор настроек для работы с продуктивом. Разработчики же свободно могут править app.config локально и работать с Debug конфигурацией (если разработчикам необходимо работать и с Release, можно добавить новую конфигурацию CIRelease, которая будет использоваться только на билдсервере).

Для использования данного подхода не в WCF проекте, нужно совершить несколько махинаций.

Создаём файл ConfigurationTransform.targets следующего содержания:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
    <PropertyGroup>
        <AllowedReferenceRelatedFileExtensions>
            $(AllowedReferenceRelatedFileExtensions);
            .dll.config
        </AllowedReferenceRelatedFileExtensions>
    </PropertyGroup>
    <PropertyGroup>
        <ResolveReferencesDependsOn>
            TransformConfig;
            $(ResolveReferencesDependsOn)
        </ResolveReferencesDependsOn>
    </PropertyGroup>
    <Target Name="TransformConfig" BeforeTargets="_CopyAppConfigFile" Condition="Exists('App.$(Configuration).config')">
        <!--Создаём трансофрмированный app config в промежуточной директории.-->
        <TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
        <!--Сообщаем, что для сборки нужно использовать только что сгенерированный файл.-->
        <ItemGroup>
            <AppConfigWithTargetPath Remove="App.config" />
            <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
                <TargetPath>$(TargetFileName).config</TargetPath>
            </AppConfigWithTargetPath>
        </ItemGroup>
    </Target>
</Project>

Кладём файл в папку решения (уровень .sln) и подключаем его сразу после Microsoft.CSharp.targets.

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\ConfigurationTransform.targets" />

Можно в принципе подключать данный таск и напрямую в .csproj без создания дополнительного файла, но с доп. файлом автоматически решается проблема с наличием нескольких проектов, для которых нужно трансформировать app.config.

Пример. Исходный app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <connectionStrings>
    <add name="Test" connectionString="" />
  </connectionStrings>
</configuration>

Файл трансформации app.Release.config:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <connectionStrings>
    <add name="Test" connectionString="ProductionConnectionString" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>
</configuration>

В файле трансформаций используется операция SetAttributes, которая задаёт атрибуты для узла, который идентифицируется по атрибуту name (xdt:Locator="Match(name)"). Другие примеры трансформаций здесь.

Вуаля, при сборке в конфигурации ConfigName, если рядом с app.config есть файл app.ConfigName.config, он будет использован для трансформации и сборка будет проведена с использованием промежуточного трансформированного файла.

Пример таска отсюда.

Еще бонусом небольшой лайфхак. Чтобы файлы трансформации в Solution Explorer не занимали много места, их можно показывать как дочерние от главного app.config:

Для этого в .csproj нужно поправить регистрацию файлов трансформации, добавив тэг DependentUpon:

<ItemGroup>
  <None Include="App.config" />
  <None Include="App.Release.config">
    <DependentUpon>App.config</DependentUpon>
  </None>
</ItemGroup>
Answer 4

Строки из app.config можно вынести в отдельный файл через атрибут configSource.

В файлах web.config пишется что-то вроде этого:

<connectionStrings configSource="bin\connectionstrings.config" />

В файлах app.config пишем вот так:

<connectionStrings configSource="connectionstrings.config" />

Во все проекты подключаем общий для всех файл connectionstrings.config как ссылку и настраиваем его копирование при сборке. В файле проекта должно получиться примерно вот это:

<Content Include="..\connectionstrings.config">
  <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

Дальше этот файл можно добавить в .gitignore, позволив тем самым каждому разработчику иметь свой вариант этого файла. (Только не забудьте положить рядом файл вроде connectionstrings.sample.config чтобы разработчикам не пришлось каждый раз заполнять его с нуля методом проб и ошибок).

При сборке этот файл надо будет генерировать на стороне сборочного сервера. Также можно при релизной сборке применить трансформацию к конфигу, заменив секцию connectionStrings целиком.

READ ALSO
C# .Net 2.0 послать get запрос

C# .Net 2.0 послать get запрос

Добрый день, вот допустим у меня есть код который посылает гет запрос

458
Как группировать по нескольким полям?

Как группировать по нескольким полям?

Пример кода взят из MSDNВ нем показано,как получить сумму ,группируя по одному полю

432
Добавление using в динамическую компиляцию

Добавление using в динамическую компиляцию

Всем доброго времениИзучая динамическую компиляцию наткнулся на проблему - при добавлении некоторых using в текст кода компилируемой программы,...

261
Как перерисовывать DataGridView после запроса LINQ

Как перерисовывать DataGridView после запроса LINQ

Есть DGV,в которую считывается таблица из бд(с помощью класса linq2sql)Визуально это выглядит вот так:

276