Cómo decirle a la aplicación que lea <tiempo de ejecución> desde mi archivo app.config personalizado en lugar del predeterminado

8

Digamos que estoy creando una aplicación llamada ConsoleApp2 .

Debido a algunas bibliotecas de terceros que estoy usando, mi archivo app.config predeterminado genera código como

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Esto se debe a que mi solución hace referencia a diferentes versiones de una biblioteca, por lo que debe decirles a todos: " Oye, si buscas alguna versión antigua de esta biblioteca, solo usa la nueva versión ". Y eso está bien.

El problema es que quiero definir un archivo de configuración separado "test.exe.config" donde tengo algunas configuraciones y deshacerme del generado automáticamente.

Para informar a mi aplicación sobre el nuevo archivo de configuración, estoy usando un código como

AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", "test.exe.config");

Y eso funciona (casi) perfectamente. Y escribí allí " casi " ya que aunque la <appSettings>sección se está leyendo correctamente, la <runtime>sección no se está mirando en mi archivo de configuración personalizado, pero la aplicación lo busca en el archivo de configuración predeterminado, lo cual es un problema ya que quiero poder borrar eso más tarde.

Entonces, ¿cómo puedo decirle a mi aplicación que lea también la <runtime>información de mi archivo de configuración personalizado?


Cómo reproducir el problema

Una muestra simple para reproducir mi problema es la siguiente:

Cree una biblioteca llamada ClassLibrary2 ( .Net Framework v4.6 ) con una sola clase de la siguiente manera

using Newtonsoft.Json.Linq;
using System;

namespace ClassLibrary2
{
    public class Class1
    {
        public Class1()
        {
            var json = new JObject();
            json.Add("Succeed?", true);

            Reash = json.ToString();
        }

        public String Reash { get; set; }
    }
}

Tenga en cuenta la referencia al paquete Newtonsoft . El que está instalado en la biblioteca es v10.0.2 .

Ahora cree una aplicación de consola llamada ConsoleApp2 ( .Net Framework v4.6 ) con una clase llamada Programa cuyo contenido es simplemente el siguiente:

using System;
using System.Configuration;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {

            AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", "test.exe.config");

            var AppSettings = ConfigurationManager.AppSettings;

            Console.WriteLine($"{AppSettings.Count} settings found");
            Console.WriteLine($"Calling ClassLibrary2: {Environment.NewLine}{new ClassLibrary2.Class1().Reash}");
            Console.ReadLine();

        }
    }
}

Esta aplicación debería haber instalado también Newtonsoft , pero en una versión diferente v12.0.3 .

Compile la aplicación en modo de depuración. Luego, en la carpeta ConsoleApp2 / ConsoleApp2 / bin / Debug, cree un archivo llamado test.exe.config con el siguiente contenido

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <appSettings>
    <add key="A" value="1"/>
    <add key="B" value="1"/>
    <add key="C" value="1"/>
  </appSettings>
</configuration>

y tenga en cuenta que en esa misma carpeta de depuración también está el archivo de configuración predeterminado ConsoleApp2.exe.config con un contenido como

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Si en este momento ejecuta la aplicación, se compilará sin problemas y debería ver una consola como

ingrese la descripción de la imagen aquí

Tenga en cuenta que la configuración (3) se leyó correctamente desde mi archivo de configuración personalizado. Hasta aquí todo bien...

Ahora cambie el nombre del archivo de configuración predeterminado a algo como _ConsoleApp2.exe.config y vuelva a ejecutar la aplicación. Ahora debería obtener una FileLoadException .

ingrese la descripción de la imagen aquí

Entonces, de nuevo, ¿cómo puedo decirle a mi aplicación que lea la <runtime>información de mi archivo de configuración personalizado?


Razón fundamental

La razón por la que estoy buscando una respuesta a esta pregunta es la siguiente:

Cuando lanzamos nuestra aplicación, colocamos todos los archivos .exe y .dll en una carpeta y nuestro archivo de configuración personalizado (con configuraciones, etc.) en otra, donde nuestros clientes tienen archivos similares.

En la carpeta con los archivos .exe y .dll, tratamos de mantener la menor cantidad posible, por lo que me pidieron que encontrara una forma de deshacerme de esa ConsoleApp2.exe.config si es posible. Ahora, dado que los enlaces mencionados anteriormente se escribieron en ese archivo de configuración, intenté mover esa información a nuestro archivo de configuración personalizado ... pero hasta ahora no he logrado: las redirecciones de enlace siempre se intentan leer desde ese ConsoleApp2.exe .config , así que tan pronto como lo elimino , obtengo excepciones ...

Deczaloth
fuente
1
Eso no es posible. El .config se aplica antes de que el host CLR cree el dominio de aplicación principal. Muy temprano, incluso antes de que se inicie el CLR y se haga cualquier otra cosa, después de lo cual la configuración se bloquea. Técnicamente, podría crear su propio AppDomain, excepto que hay poco futuro para tal solución ya que .NETCore y el próximo .NET 5 ya no los admiten. El alojamiento personalizado tampoco es exactamente ideal. No es obvio por qué es necesario tal pirateo, las redirecciones vinculantes son un mero detalle de implementación.
Hans Passant
Hola @HansPassant, gracias por tu información. ¿Conoces algún lugar del documento de MSDN donde esté documentado? Además, si lo publica como respuesta, me complacerá aceptarlo y darle la recompensa.
Deczaloth
2
Libros, Steven Pratschner ha escrito uno que llega a lo esencial. Difícil de recomendar, el CLR cambió demasiado desde entonces. Las respuestas "Eso no es posible" no se consideran útiles para nadie que visite SO. No hay una manera obvia de llevarte a algún lado ya que no explicaste por qué necesitas hacer esto.
Hans Passant
@HansPassant, edité mi pregunta agregando la "justificación" al final.
Deczaloth
2
@Deczaloth Tengo la sensación de que tenemos un problema XY : está tratando de encontrar una solución para aplicar la <runtime>sección desde otra configuración, pero el problema ocurre debido a la forma en que administra la configuración común. Si administra la configuración común de una manera diferente, este problema ya no es relevante. Verifique mi respuesta y considere usar transformaciones de configuración en lugar de ajustar el tiempo de ejecución.
fenixil

Respuestas:

6

Probablemente estés buscando transformaciones de configuración :

La idea detrás es crear múltiples configuraciones en Visual Studio como Debug, Release, Production, Test ... en el administrador de configuración y un archivo de configuración predeterminado más las llamadas transformaciones.

Tenga en cuenta que puede crear tantas configuraciones como desee en el administrador de configuración. Para agregar nuevos, haga clic en Configuraciones de soluciones (el menú desplegable que muestra "Debug" o "Release") y seleccione "Configuration Manager ...". Ábralo y verá una lista de todas las configuraciones existentes actualmente. Despliegue el cuadro combinado "Configuración de solución activa" y seleccione " <New...>" para agregar más.

Esas transformaciones especifican lo que hace que la configuración específica sea diferente de la predeterminada, por lo que no necesita repetir lo que ya ha especificado en la configuración predeterminada, en su lugar solo menciona las diferencias, por ejemplo:

<configuration>
    <appSettings>
        <add key="ClientSessionTimeout" value="100"
            xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
    </appSettings>
</configuration>

que encuentra la configuración relevante por su clave ClientSessionTimeouty establece su valor 100reemplazando el valor original en el archivo de configuración (esto es lo que xdt:Transform="SetAttributes" xdt:Locator="Match(key)"significan los atributos de transformación adicionales ). También puede especificar eliminar configuraciones existentes (especificando en su xdt:Transform="Remove"lugar), por ejemplo

<add key="UserIdForDebugging" xdt:Transform="Remove" xdt:Locator="Match(key)"/>

eliminaría un ID de usuario que debería estar allí solo para la depuración, no para la versión (para obtener más información sobre las opciones disponibles, consulte aquí , descrito para Web.config, pero también aplicable para App.config).

Además del App.Configarchivo, tiene un archivo por configuración, es decir ,App.Debug.Config para depuración, App.Release.Configpara lanzamiento, etc. Visual Studio lo ayuda a crearlos.

Ya he creado respuestas en StackOverflow aquí y allá , que lo describe en detalle, por favor, eche un vistazo.

Si tiene problemas para mostrarlos en Visual Studio, eche un vistazo aquí .


Con respecto a su justificación :

Las transformaciones están creando un archivo de configuración completo aplicando el archivo de transformación al archivo de configuración predeterminado. El archivo resultante se compila y se coloca en la carpeta "bin", junto con los otros archivos compilados. Por lo tanto, si tiene una configuración "Release" seleccionada, todos los archivos, incluido el archivo de configuración transformado, se compilan en "bin \ Release".

Y el archivo de configuración se nombra como el archivo exe más ".config" al final (en otras palabras, no hay ".Release.config" en la carpeta binaria, sino un "MySuperCoolApp.exe.config" creado - para la aplicación "MySuperCoolApp.exe").

Del mismo modo, lo mismo es cierto para la otra configuración: cada configuración crea una subcarpeta dentro de "bin". Si está utilizando scripts, se puede hacer referencia a esa subcarpeta como $(TargetDir)en un evento posterior a la compilación.

Mate
fuente
Hola Matt, gracias por tomarte tu tiempo para ayudarme. Como puede ver en una respuesta a continuación, @fenixil sugirió también transformaciones de configuración. Sin embargo, y aunque lo encuentro una herramienta fascinante (¡definitivamente lo probaré en nuestros proyectos!) No puedo ver cómo esto resuelve mi pregunta. ¿Podría extender mi muestra súper simple para implementar transformaciones de configuración de una manera que resuelva mi problema? (tenga en cuenta mi "justificación" al final de mi pregunta)
Deczaloth
@Deczaloth: agregó algunos consejos para su Justificación, espero que esto lo aclare más.
Matt
4

Transformación de la configuración

Dado que el problema ocurre cuando intenta usar otro archivo de configuración (no nativo), está tratando de encontrar una solución para sustituirlo 'adecuadamente'. En mi respuesta, quiero dar un paso atrás y centrarme en la razón por la que desea sustituirlo. Según lo que describió en la pregunta, tiene que definir configuraciones de aplicación personalizadas. Si entendí correctamente, planea vincularlo al proyecto de destino, establezca la propiedad 'Copiar a la salida' en 'Siempre' y lo obtendrá cerca de la aplicación.

En lugar de copiar el nuevo archivo de configuración, hay una manera de transformar uno existente (nativo), en su caso, ConsoleApp2.exe.configutilizando transformaciones Xdt . Para lograr eso, crea un archivo de transformación y declara allí solo las secciones que desea transformar, por ejemplo:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings xdt:Transform="Replace">
    <add key="A" value="1"/>
    <add key="B" value="1"/>
    <add key="C" value="1"/>
  </appSettings>
</configuration>

Los beneficios de dicho enfoque son:

  • flexibilidad: las transformaciones son muy flexibles , puede reemplazar secciones, fusionarlas, establecer / eliminar atributos, etc. Es posible que tenga transformaciones específicas del entorno (DEV / UAT / PROD) o compilaciones específicas (Debug / Release).
  • reutilización: defina la transformación una vez y reutilícela en todos los proyectos que necesite.
  • granularidad: declara solo lo que necesita, no necesita copiar y pegar toda la configuración.
  • seguridad: permite que nuget y msbuild administren el archivo de configuración 'nativo' (agregue redireccionamientos vinculantes, etc.)

La única desventaja de este enfoque es la curva de aprendizaje: debe aprender la sintaxis y saber cómo pegar las transformaciones a sus configuraciones en MSBuild.

.NET Core admite la transformación, aquí hay un ejemplo de cómo crear transformaciones para web.config, pero puede aplicar transformaciones a cualquier configuración.

Si desarrolla aplicaciones .NET (no .NET Core), le recomendaría que consulte Slowcheetah .

Hay muchos recursos y blogs útiles sobre la transformación, es bastante utilizado. Por favor contáctame si tienes dificultades.

Desde mi punto de vista, las transformaciones de configuración son una solución adecuada para lograr su objetivo, por lo que le recomiendo considerarlo en lugar de modificar el tiempo de ejecución.

Externalizar secciones de configuración

Si aún desea mantener appSettings en una ubicación común, puede externalizar las secciones de configuración con el atributo ConfigSource . Mira esto y este hilo para más detalles:

// ConsoleApp2.exe.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings configSource="../commonConfig/connections.config"/>
</configuration>

// connections.config:
<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
<add name="MovieDBContext" 
   connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=aspnet-MvcMovie;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\Movies.mdf" 
   providerName="System.Data.SqlClient" 
/>
</connectionStrings>

La sección AppSettings contiene el atributo File que le permite fusionar parámetros de otro archivo.

Esta opción le permite reemplazar ciertas secciones de la configuración, pero no todo el contenido en sí. Entonces, si solo necesita appSettings, es totalmente aplicable: simplemente coloque el archivo de configuración con appSettings en una ubicación común compartida con el usuario y el archivo de configuración de parche (agregar fileo configSourceatributo) para obtener esta sección desde esa ubicación. Si necesita más secciones, deberá extraerlas como archivos separados.

fenixil
fuente
¡Oye! Gracias por tomarse su tiempo para leer / responder mi pregunta.
Probaré
Eché un vistazo a las transformaciones de configuración. Es una herramienta muy interesante, debo admitir. Sin embargo, no veo cómo esto responde a mi pregunta. Si entendí bien, propone que agregue un archivo adicional (es decir, el archivo de transformación de configuración). E incluso dejando eso de lado, si escribo un archivo de transformación de configuración para que la sección <runtime> se escriba automáticamente en mi archivo de configuración personalizado, todavía no sé cómo decirle a la aplicación que lea las redirecciones de enlace desde allí en lugar de desde el archivo de configuración predeterminado Si hay algo que parece no haber entendido, avíseme.
Deczaloth
1
La idea detrás de la transformación de configuración es que ya no tiene ningún archivo de configuración personalizado y esto elimina la necesidad de leer la configuración de tiempo de ejecución de otro archivo. Simplemente ponga la configuración de su aplicación (o cualquier otra cosa) con transformaciones al archivo de configuración nativo. ¿Tiene sentido?
fenixil
Lamento mucho decir esto, pero aún no entiendo el punto. ¿Podría usar mi muestra súper simple de arriba para producir un ejemplo completo de su propuesta? Entonces (espero) veré claramente cómo Config Transform puede resolver mi problema :)
Deczaloth
depende totalmente de qué considerar un problema: desde su perspectiva, es incapaz de reemplazar el archivo de configuración en tiempo de ejecución para que la sección <tiempo de ejecución> se consuma de otro archivo. Estoy tratando de replantear su preocupación: si no necesita reemplazar el archivo de configuración en tiempo de ejecución, entonces no tiene este problema. Entonces, desde mi punto de vista, el problema es cómo gestiona la configuración para que necesite hacer estos hacks con sustitución en tiempo de ejecución.
fenixil
3

Para trabajar correctamente con un .configarchivo diferente , puede mantener el predeterminado para administrar redireccionamientos de ofertas y otro para los parámetros de su aplicación. Para hacerlo, cambiar app.config predeterminado en tiempo de ejecución se ve muy bien.

También puede cerrar la generación automática de redireccionamiento de enlace y usar solo un archivo app.config hecho a mano. Aquí hay un ejemplo: necesita una forma de hacer referencia a 2 versiones diferentes de la misma DLL de terceros

Editar Teniendo en cuenta la justificación: si lo entiendo, no desea el archivo app.exe.config en absoluto. Ya logras poner y leer contenido personalizado en otro lugar.

Solo queda la redirección vinculante.

Puede deshacerse de él administrando la redirección de enlace en tiempo de ejecución como se hace aquí: https://stackoverflow.com/a/32698357/361177 También puede recrear un solucionador de enlace configurable haciendo que su código mire el archivo de configuración.

Mis dos centavos aquí: es factible pero no creo que valga la pena.

Edición 2 Esta solución parece prometedora https://stackoverflow.com/a/28500477/361177

Orace
fuente
Hola, gracias por tu respuesta! Voté por la referencia a la respuesta de @BryanSlatner, sin embargo, esa solución no es exactamente lo que estaba pidiendo. Y podría ser que lo que quiero lograr, a saber: poder decirle a mi aplicación que lea los redireccionamientos vinculantes de mi archivo de configuración personalizado, ni siquiera es posible ...
Deczaloth
1
Es posible que pueda reconstruir el proceso de redireccionamiento de enlace configurable. Al registrar un método en el AppDomain.CurrentDomain.AssemblyResolveevento y hacer que este método obtenga las reglas de enlace del archivo de configuración.
Orace
1
Releyé tu razonamiento. Parece que su objetivo (exe y config separados) lo llevará a codificar donde el programa encontrará su configuración. Lo siento pero me parece tonto. Si la ruta [relativa] del archivo de configuración cambia, deberá volver a compilar su código. Entiendo los beneficios de una división, y para mí la mejor solución es un archivo app.config con la configuración de tiempo de ejecución y la ruta [relativa] a otro archivo de configuración, este otro archivo contendrá el resto de la configuración donde el cliente puede Ajustar la aplicación.
Orace
Entiendo tu punto. No obstante, la estructura de carpetas donde se ubicará el archivo de configuración no ha cambiado durante casi más de una década, por lo que eso no sería un problema. De todos modos, creo que al final aplicaríamos tal enfoque: "archivo app.config con la configuración de tiempo de ejecución y la ruta [relativa] a otro archivo de configuración".
Deczaloth
@Deczaloth echa un vistazo aquí stackoverflow.com/a/28500477/361177
Orace