2008-09-25 22 views
159

Tengo una aplicación .NET que tiene diferentes archivos de configuración para compilaciones de Debug y Release. P.ej. el archivo debug app.config apunta a un desarrollo SQL Server que tiene habilitada la depuración y el objetivo de lanzamiento apunta al servidor SQL en vivo. También hay otras configuraciones, algunas de las cuales son diferentes en depuración/versión..NET Configuration (app.config/web.config/settings.settings)

Actualmente utilizo dos archivos de configuración por separado (debug.app.config y release.app.config). Tengo un evento de compilación en el proyecto que dice que si se trata de una versión de lanzamiento, copie release.app.config en app.config, o copie debug.app.config en app.config.

El problema es que la aplicación parece obtener su configuración del archivo settings.settings, así 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 la configuración .settings y tiene que reconstruir para hacer que use la configuración correcta.

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

Respuesta

7

Mi empleador actual resolvió este problema colocando primero el nivel dev (depuración, etapa, directo, etc.) en el archivo machine.config. Luego escribieron el 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 funciona para ellos.

+1

En realidad, yo creo que es bastante maldito elegante, ya que me gusta mantener las diferentes versiones de configuración visible dentro de una solución, incluso si no son en vivo. – annakata

+1

Esta es una solución muy intrigante. Me encantaría ver un ejemplo de esto en acción. –

32

Hay una pregunta relacionada aquí:

Improving Your Build Process

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

<appSettings file="Local.config"> 

En lugar de comprobar en dos archivos (o más), se solo compruebe el archivo de configuración predeterminado, y luego en cada máquina de destino, coloca un Local.config, solo con la sección appSettings que tiene las anulaciones 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 comprobar que en alguna parte, pero no como parte de las soluciones reales. Cada desarrollador pone un "ignorar" en el archivo Local.config para que no se compruebe, lo que sobrescribiría el archivo de los demás.

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

14

De lo que estoy leyendo, parece que usted está utilizando Visual Studio en el proceso de construcción. ¿Ha pensado en utilizar MSBuild y Nant lugar?

sintaxis XML de Nant es un poco raro, pero una vez que entienda que, haciendo lo que usted ha mencionado 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> 
5

una de las soluciones que funcionaron bien me estaba usando un WebDeploymentProject. tuve 2/3 archivos web.config diferentes en mi sitio, y en publicar, dependiendo del modo de configuración seleccionado (versión/staging/etc ...) Copiaría sobre Web.Release.config y lo cambiaría a web.config en el evento AfterBuild, y eliminaría 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> 
8

Utilizamos proyectos de Despliegue Web pero desde entonces hemos migrado a NAnt. En lugar de ramificación y la copia de diferentes archivos de configuración que actualmente integrar los valores de configuración directamente en el script de construcción e inyectarlos 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 cualquiera de los casos, los archivos de configuración puede tener lo que los valores desarrollador quieren y funcionarán bien desde su entorno de desarrollo sin romper sus sistemas de producción. Hemos descubierto que es menos probable que los desarrolladores cambien arbitrariamente las variables de script de compilación cuando prueban cosas, por lo que las configuraciones erróneas accidentales han sido más raras que con otras técnicas que hemos probado, aunque es necesario agregar cada var en el proceso para que el valor dev no se empuja a prod por defecto.

59

Cualquier configuración que podrían diferir entornos en todo debería almacenan en el nivel máquina, no el nivel aplicación. (More info on configuration levels.)

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

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

Y, obviamente, el contenido del directorio CONFIG a nivel de máquina obtiene la versión controlada en un repositorio diferente o una estructura de carpeta diferente de la suya. Puede hacer que sus archivos .config sean más amigables con el control de 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 empresas diferentes. (No estoy tratando de alardear, solo quiero que sepa que nunca he visto una situación en la que este enfoque no funcione.)

+3

¿Qué tal una situación en la que no controlas el servidor web y, por lo tanto, no puedes cambiar la configuración del nivel de la máquina? Los ejemplos incluyen un servidor web de terceros o un servidor web compartido entre múltiples departamentos en una empresa. – RationalGeek

+1

No funcionaría. 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 intento ser insensible en absoluto. Realmente creo que si estás trabajando en un servidor web compartido, debes volver a evaluarlo. – Portman

+7

La mayoría de las empresas en las que he trabajado con sitios internos alojan varias aplicaciones en un servidor; allí debería realizarse una reevaluación a nivel corporativo – MPritchard

2

Como usted, también configuré 'multi' app.config - por ej. app.configDEV, app.configTEST, app.config.LOCAL. Veo a algunos de la excelente alternativa sugerida, pero si te gusta la forma en que funciona para usted, me gustaría añadir lo siguiente:

Tengo un
<appSettings>
<add key = "Env" value = "[Local] "/> para cada aplicación que añadir a la UI 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 muchas bases de datos/configuración de wcf frente a 4 momentos). Para implementar con clickonce en cada uno, cambio 4 seetings 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á 'bloqueada' después de un cambio de nombre manual. (Lo cual creo que le solucionará el problema de configuración).

Me parece que esto funciona muy bien (un día voy a conseguir tiempo para mirar MSBuild/Nant)

3

Nuestra proj tiene el mismo problema por el que tuvimos que mantener 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 MSBuild Community tasks. Incluye la tarea 'XmlMassUpdate' que puede 'actualizar en masa' las entradas en cualquier archivo XML una vez que le das el nodo correcto para empezar.

de implementar:

1) Es necesario tener un archivo de configuración que tendrá sus entradas env dev; este es el archivo de configuración en su solución.

2) Debe tener un archivo 'Substitutions.xml', que contenga únicamente 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 no serán tocados por la tarea

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.

Consulte el siguiente ejemplo:

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

reemplazar '$ Medio Ambiente' con 'control de calidad' o 'Prod' basado en lo 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 real 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 despliegue y ¡listo!

Para una mejor visión, lee esto:

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

49

Esto podría ayudar a algunas personas que se ocupan de Settings.settings y App.config: Cuidado con los atributos GenerateDefaultValueInCode en el panel de propiedades durante la edición de cualquiera de los valores en la grilla Settings.settings en Visual Studio (Visual Studio 2008 en mi caso).

Si establece GenerateDefaultValueInCode en True (True es el valor predeterminado aquí), el valor predeterminado se compila en el EXE (o DLL), puede encontrarlo embebido en el archivo cuando lo abra en un editor de texto sin formato.

Estaba trabajando en una aplicación de consola y si tenía los valores predeterminados en el EXE, la aplicación siempre ignoraba el lugar del archivo de configuración en el mismo directorio. Toda una pesadilla y no hay información sobre esto en todo Internet.

+7

Esto es precisamente lo que me sucedió este fin de semana pasado. Saqué mucho pelo tratando de descubrir por qué mi aplicación parecía ignorar mi archivo app.config. Se supone que debe conectarse a un servicio web y la URL del servicio está en mi app.config. Desconocido para mí, cuando creé la referencia web, también creó un archivo Settings.Settings AND 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 duro y se incrustó en el archivo ejecutable.¡¡MUY FRUSTRANTE!! Gracias a esta publicación, ahora puedo deshacerme de esa "característica" –

+0

+1 Esta respuesta es la ** crítica **: si quieres que tu configuración vaya al archivo app.config, configura su atributo GenerateDefaultValueInCode en False (El defecto es cierto). – Sabuncu

0

Dice asp.net arriba, entonces ¿por qué no guardar su configuración en la base de datos y usar un caché personalizado para recuperarlos?

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

Ejemplo de un caché personalizado:

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 
    } 
} 
Cuestiones relacionadas