Configuración de .NET (app.config / web.config / settings.settings)

162

Tengo una aplicación .NET que tiene diferentes archivos de configuración para las versiones Debug y Release. Por ejemplo, el archivo app.config de depuración apunta a un SQL Server de desarrollo que tiene habilitada la depuración y el objetivo de lanzamiento apunta al SQL Server en vivo. También hay otras configuraciones, algunas de las cuales son diferentes en depuración / lanzamiento.

Actualmente uso dos archivos de configuración separados (debug.app.config y release.app.config). Tengo un evento de compilación en el proyecto que dice que si se trata de una compilación de lanzamiento, copie release.app.config en app.config; de lo contrario, copie debug.app.config en app.config.

El problema es que la aplicación parece obtener su configuración del archivo settings.settings, por lo que tengo que abrir settings.settings en Visual Studio, que luego me indica que la configuración ha cambiado, así que acepto los cambios, guardo settings.settings y tengo reconstruir para hacer que use la configuración correcta.

¿Existe un método mejor / recomendado / preferido para lograr un efecto similar? O igualmente, ¿me he acercado a esto completamente mal y hay un mejor enfoque?

Gavin
fuente
Quiero deshabilitar la depuración en Windows, lo he intentado desmarcando todas las casillas de verificación en la configuración de depuración, pero aún así puedo depurar el exe de liberación bin. Alguien me ayude en esto ...
Vinoth Narayan

Respuestas:

62

Cualquier configuración que pueda diferir entre entornos debe almacenarse a nivel de máquina , no a nivel de aplicación . (Más información sobre los niveles de configuración).

Estos son los tipos de elementos de configuración que normalmente almaceno a nivel de máquina:

Cuando cada entorno (desarrollador, integración, prueba, etapa, en vivo) tiene su propia configuración única en el directorio c: \ Windows \ Microsoft.NET \ Framework64 \ v2.0.50727 \ CONFIG , puede promocionar el código de su aplicación entre entornos sin ningún modificaciones posteriores a la construcción.

Y, obviamente, el contenido del directorio CONFIG a nivel de máquina se controla por versión en un repositorio diferente o en una estructura de carpetas diferente de su aplicación. Puede hacer que sus archivos .config sean más amigables con el control de la fuente a través del uso inteligente de configSource .

He estado haciendo esto durante 7 años, en más de 200 aplicaciones ASP.NET en más de 25 compañías diferentes. (No estoy tratando de presumir, solo quiero hacerle saber que nunca he visto una situación en la que este enfoque no funcione).

Portman
fuente
3
¿Qué pasa con una situación en la que no controlas el servidor web y, por lo tanto, no puedes cambiar la configuración a nivel de máquina? Los ejemplos incluyen un servidor web de terceros o un servidor web compartido entre varios departamentos en una empresa.
RationalGeek
1
No funcionaria Pero en la era de las máquinas virtuales, Amazon EC2 y los servidores de $ 400 de Dell, ¿alguien realmente hace algo serio en las máquinas compartidas? No trato de ser insensible en absoluto: realmente creo que si está trabajando en un servidor web compartido, debe reevaluarlo.
Portman
77
La mayoría de las empresas en las que he trabajado con sitios internos alojan múltiples aplicaciones en un servidor; allí debería realizarse una reevaluación a nivel corporativo
MPritchard
Múltiples aplicaciones en un servidor están bien siempre que las aplicaciones estén todas en el mismo "entorno". Es decir, no querría la instancia LIVE de App1 en el mismo servidor que la instancia DEV de App2. Por ejemplo, su configuración SMTP se compartiría en todas sus aplicaciones. En producción, apunta a un servidor de correo real; en desarrollo, apunta a un archivo en el disco.
Portman
77
Sé que esto funcionará, pero todavía va en contra de lo que recomendaría al intentar automatizar la implementación. Creo que las configuraciones son específicas de la aplicación, necesitan ser controladas por la versión junto con la aplicación y evolucionar junto con ella. Confiar en la configuración de la máquina solo cambia, y en mi opinión lo hace más difícil. Me gusta mantener juntas las cosas que cambian juntas y desplegarlas juntas. Si agrego una nueva configuración para Dev, probablemente necesite una equivalente para prod.
Miguel Madero
51

Esto podría ayudar a algunas personas que se ocupan de Settings.settings y App.config: tenga cuidado con el atributo GenerateDefaultValueInCode en el panel Propiedades mientras edita cualquiera de los valores en la cuadrícula Settings.settings en Visual Studio (Visual Studio 2008 en mi caso).

Si configura GenerateDefaultValueInCode en True (¡True es el valor predeterminado aquí!), El valor predeterminado se compila en el EXE (o DLL), puede encontrarlo incrustado en el archivo cuando lo abre en un editor de texto sin formato.

Estaba trabajando en una aplicación de consola y si había omitido el EXE, ¡la aplicación siempre ignoraba el archivo de configuración ubicado en el mismo directorio! Toda una pesadilla y no hay información sobre esto en todo Internet.

romano
fuente
77
Esto es precisamente lo que me pasó el pasado fin de semana. ¡Saqué mucho pelo tratando de entender por qué mi aplicación parecía ignorar mi archivo app.config! Se supone que se conecta a un servicio web y la url del servicio está en mi app.config. Sin saberlo, cuando creé la referencia web, también creó un archivo Settings.Settings Y codificó el valor predeterminado en el código. Incluso cuando finalmente descubrí (y eliminé) el archivo de configuración, ese valor predeterminado permaneció en el código y se incrustó en el exe. ¡¡MUY FRUSTRANTE!! Gracias a esta publicación, ahora puedo deshacerme de esa "característica"
Mike K
+1 Esta respuesta es crítica : si desea que su configuración vaya al archivo app.config, configure su atributo GenerateDefaultValueInCode en False (el valor predeterminado es True).
Sabuncu
34

Hay una pregunta relacionada aquí:

Mejorando su proceso de construcción

Los archivos de configuración vienen con una forma de anular la configuración:

<appSettings file="Local.config">

En lugar de registrar dos archivos (o más), solo registra el archivo de configuración predeterminado y luego, en cada máquina de destino, coloca un Local.config, con solo la sección appSettings que tiene las modificaciones para esa máquina en particular.

Si está utilizando secciones de configuración, el equivalente es:

configSource="Local.config"

Por supuesto, es una buena idea hacer copias de seguridad de todos los archivos Local.config de otras máquinas y verificarlos en algún lugar, pero no como parte de las soluciones reales. Cada desarrollador pone un "ignorar" en el archivo Local.config para que no se registre, lo que sobrescribiría el archivo de todos los demás.

(En realidad no tiene que llamarlo "Local.config", eso es justo lo que uso)

Eric Z Beard
fuente
14

Por lo que estoy leyendo, parece que estás usando Visual Studio para tu proceso de compilación. ¿Has pensado en usar MSBuild y Nant en su lugar?

La sintaxis xml de Nant es un poco extraña, pero una vez que la entiendes, hacer lo que mencionaste se vuelve bastante trivial.

<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>
Steven Williams
fuente
8

Solíamos usar proyectos de implementación web, pero desde entonces hemos migrado a NAnt. En lugar de ramificar y copiar diferentes archivos de configuración, actualmente incorporamos los valores de configuración directamente en el script de compilación y los inyectamos en nuestros archivos de configuración a través de tareas xmlpoke:

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

En cualquier caso, sus archivos de configuración pueden tener los valores de desarrollador que desee y funcionarán bien desde su entorno de desarrollo sin romper sus sistemas de producción. Descubrimos que es menos probable que los desarrolladores cambien arbitrariamente las variables del script de compilación al probar cosas, por lo que las configuraciones erróneas accidentales han sido más raras que con otras técnicas que hemos probado, aunque todavía es necesario agregar cada var temprano en el proceso para que el valor de desarrollo no se empuja a la producción por defecto.

jasondoucette
fuente
7

Mi empleador actual resolvió este problema colocando primero el nivel de desarrollo (depuración, etapa, directo, etc.) en el archivo machine.config. Luego escribieron un código para recogerlo y usar el archivo de configuración correcto. Eso resolvió el problema con la cadena de conexión incorrecta después de que se implementa la aplicación.

Recientemente escribieron un servicio web central que devuelve la cadena de conexión correcta del valor en el valor machine.config.

¿Es esta la mejor solución? Probablemente no, pero les funciona.

Hector Sosa Jr
fuente
1
En realidad, creo que es bastante elegante, ya que me gusta mantener las diversas versiones de config visibles dentro de una solución, incluso si no están en vivo.
annakata
1
Esta es una solución muy intrigante. Me encantaría ver un ejemplo de esto en acción.
Mike K
5

Una de las soluciones que me funcionó bien fue usar un WebDeploymentProject. Tenía 2/3 archivos diferentes de web.config en mi sitio, y en la publicación, dependiendo del modo de configuración seleccionado (lanzamiento / puesta en escena / etc ...) Copiaría en Web.Release.config y cambiaría el nombre a la web. config en el evento AfterBuild y elimine los que no necesito (Web.Staging.config, por ejemplo).

<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>
Adam Vigh
fuente
3

Nuestro proyecto tiene el mismo problema en el que tuvimos que mantener las configuraciones para dev, qa, uat y prod. Esto es lo que seguimos (solo se aplica si está familiarizado con MSBuild):

Use MSBuild con la extensión de tareas de la Comunidad MSBuild. Incluye la tarea 'XmlMassUpdate' que puede 'actualizar en masa' las entradas en cualquier archivo XML una vez que le da el nodo correcto para comenzar.

Para implementar:

1) Debe tener un archivo de configuración que tendrá sus entradas de env de desarrollo; Este es el archivo de configuración en su solución.

2) Debe tener un archivo 'Substitutions.xml', que contiene solo las entradas que son DIFERENTES (appSettings y ConnectionStrings principalmente) para cada entorno. Las entradas que no cambian en el entorno no necesitan colocarse en este archivo. Pueden vivir en el archivo web.config de la solución y la tarea no les afectará.

3) En su archivo de compilación, simplemente llame a la tarea de actualización masiva XML y proporcione el entorno adecuado como parámetro.

Ver ejemplo a continuación:

    <!-- 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>

reemplace '$ Environment' con 'QA' o 'Prod' en función de qué env. estás construyendo para Tenga en cuenta que debe trabajar en una copia de un archivo de configuración y no en el archivo de configuración en sí para evitar posibles errores no recuperables.

Simplemente ejecute el archivo de compilación y luego mueva el archivo de configuración actualizado a su entorno de implementación y listo.

Para una mejor visión general, lea esto:

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

Punit Vora
fuente
2

Como usted, también configuré 'multi' app.config, por ejemplo, app.configDEV, app.configTEST, app.config.LOCAL. Veo algunas de las excelentes alternativas sugeridas, pero si le gusta cómo funciona para usted, agregaría lo siguiente:

Tengo un
<appSettings>
<add key = "Env" value = "[Local] "/> para cada aplicación, agrego esto a la interfaz de usuario en la barra de título: desde ConfigurationManager.AppSettings.Get ("Env");

Acabo de cambiar el nombre de la configuración a la que estoy apuntando (tengo un proyecto con 8 aplicaciones con mucha configuración de base de datos / wcf en 4 eventos). Para implementar con clickonce en cada uno, cambio 4 salidas en el proyecto y listo. (esto me encantaría automatizar)

Mi único problema es recordar 'limpiar todo' después de un cambio, ya que la configuración anterior está 'atascada' después de un cambio de nombre manual. (Lo cual creo que te solucionará el problema de configuración.configuración).

Creo que esto funciona muy bien (algún día tendré tiempo de mirar MSBuild / NAnt)

Tony Trembath-Drake
fuente
0

Web.config:

Web.config es necesario cuando desea alojar su aplicación en IIS. Web.config es un archivo de configuración obligatorio para IIS para configurar cómo se comportará como un proxy inverso frente a Kestrel. Debe mantener un web.config si desea alojarlo en IIS.

AppSetting.json:

Para todo lo demás que no concierne a IIS, usa AppSetting.json. AppSetting.json se utiliza para el alojamiento Asp.Net Core. ASP.NET Core utiliza la variable de entorno "ASPNETCORE_ENVIRONMENT" para determinar el entorno actual. De manera predeterminada, si ejecuta su aplicación sin establecer este valor, automáticamente cambiará al entorno de producción y usará el archivo "AppSetting.production.json". Cuando depura a través de Visual Studio, establece el entorno en Desarrollo, por lo que utiliza "AppSetting.json". Consulte este sitio web para comprender cómo configurar la variable de entorno de alojamiento en Windows.

App.config:

App.config es otro archivo de configuración utilizado por .NET que se utiliza principalmente para formularios Windows Forms, servicios de Windows, aplicaciones de consola y aplicaciones WPF. Cuando inicia su alojamiento Asp.Net Core a través de la aplicación de consola, también se usa app.config.


TL; DR

La elección del archivo de configuración está determinada por el entorno de alojamiento que elija para el servicio. Si está utilizando IIS para alojar su servicio, use un archivo Web.config. Si está utilizando cualquier otro entorno de alojamiento, use un archivo App.config. Consulte la documentación de Configuración de servicios mediante archivos de configuración y también consulte Configuración en ASP.NET Core.

Alper Ebicoglu
fuente
0

Dice asp.net arriba, entonces, ¿por qué no guardar sus configuraciones en la base de datos y usar un caché personalizado para recuperarlas?

La razón por la que lo hicimos porque es más fácil (para nosotros) actualizar la base de datos continuamente que obtener permiso para actualizar continuamente los archivos de producción.

Ejemplo de una caché personalizada:

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
    }
}
Prisionero CERO
fuente