Web Config Transform no funciona

88

En una aplicación .NET MVC 3.0 tengo la siguiente configuración en appSettings:

web.config

<appSettings>
<add key="SMTPHost" value="mail.domain.com"/>
    <add key="SMTPUsername" value="[email protected]"/>
    <add key="SMTPPort" value="25"/>
    <add key="SMTPPwd" value="mypassword"/>
    <add key="EmailFrom" value="[email protected]"/>
</appSettings>

Para la depuración, tengo definida la siguiente transformación de configuración:

web.Debug.config

<appSettings>
    <add  key="SMTPPort" value="58" xdt:Transform="Replace" xdt:Locator="Match(key)" />
</appSettings>

Y ejecuto la aplicación en modo de depuración, pero mi puerto SMTP todavía toma el valor del web.config, noweb.Debug.config .

¿Alguien puede sugerir qué podría estar mal en esta configuración?

HaBo
fuente

Respuestas:

156

Las transformaciones de Web.config solo se aplican como parte de una operación de publicación.

Si desea que esto se haga como parte de una app.configoperación de compilación, puede usar el complemento de Visual Studio SlowCheetah - XML ​​Transforms:

http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5

devdigital
fuente
1
Muchas gracias, me ahorraste mucho tiempo.
HaBo
3
wow me tomó 2 horas encontrar esta respuesta. Gracias por publicarlo, me habría estado tirando de los pelos.
Maní
Parece que en Visual Studio 2015 (Web) .config las transformaciones ahora son una función incorporada, por lo que ya no necesita SlowCheetah. Pero las transformaciones integradas solo se aplicarán si publica la aplicación, no si la ejecuta. Puedes ver aquí cómo lo resolví.
Matt
1
No estoy seguro de por qué usar esto, mientras que la respuesta de @ komsky proporciona una solución simple y limpia.
Csaba Toth
1
SlowCheetah es genial, pero de su propia documentación: "Para proyectos web, los archivos se transforman cuando publica o empaqueta su aplicación". En otras palabras, no durante la depuración.
Doug
31

Visual Studio (2010 - 2019) desafortunadamente no lo admite directamente mientras está depurando, solo está destinado a la publicación; incluso con la extensión SlowCheetah (respuesta marcada) no funciona para mí (solo para proyectos que usan app.config en lugar de web.config).

Tenga en cuenta que hay una solución alternativa descrita en codeproject .

Describe cómo modificar el archivo .msproj para sobrescribir el web.config actual por la versión transformada.

Primero describiré esa solución como Opción 1 , pero recientemente descubrí otra Opción 2 , que es más fácil de usar (por lo que puede desplazarse hacia abajo hasta la opción 2 directamente si lo desea):


Opción 1: agregué las instrucciones tomadas del artículo del proyecto de código original (vea el enlace de arriba), porque las capturas de pantalla ya se han ido y no quiero perder toda la información:

VS.Net no realiza ninguna transformación cuando está desarrollando y simplemente depurando su entorno local. Pero hay algunos pasos que puede seguir para que esto suceda si lo desea.

  • Primero, cree las configuraciones que desee en VS.Net , asumiendo que la depuración y la liberación predeterminadas no son suficientes para lo que está tratando de lograr.
  • Haga clic con el botón derecho en su web.configy seleccione Agregar transformaciones de configuración ; esto creará una configuración de transformación dependiente para cada una de sus configuraciones definidas.
  • Ahora puedes cambiar el nombre de tu web.configa web.base.config.
  • Agrega un web.configa tu proyecto. No importa lo que está en ella, ya que se sobrescribirá cada vez que hace una compilación pero queremos que parte del proyecto de manera VS.Net no nos la da "su proyecto no está configurado para la depuración" emergente arriba.
  • Edite su .csprojarchivo de proyecto y agregue la siguiente TransformXmltarea al destino AfterBuild. Aquí puede ver que voy a transformar el web.base.configarchivo usando el web.[configuration].configy lo guardaré como web.config. Para obtener más información, consulte estas preguntas y respuestas de Microsoft y, para obtener instrucciones sobre cómo extender la compilación, consulte allí .

Opcion 2:

Basado en esta respuesta, he desarrollado una aplicación de consola simple, TransformConfig.exe (en la sintaxis de C # 6.0):

using System;
using System.Linq;
using Microsoft.Web.XmlTransform;

namespace TransformConfig
{

  class Program
  {
    static int Main(string[] args)
    {
        var myDocumentsFolder = $@"C:\Users\{Environment.UserName}\Documents";
        var myVsProjects = $@"{myDocumentsFolder}\Visual Studio 2015\Projects";

        string srcConfigFileName = "Web.config";
        string tgtConfigFileName = srcConfigFileName;
        string transformFileName = "Web.Debug.config";
        string basePath = myVsProjects + @"\";
        try
        {

            var numArgs = args?.Count() ?? 0;
            if (numArgs == 0 || args.Any(x=>x=="/?"))
            {
                Console.WriteLine("\nTransformConfig - Usage:");
                Console.WriteLine("\tTransformConfig.exe /d:tgtConfigFileName [/t:transformFileName [/s:srcConfigFileName][/b:basePath]]");
                Console.WriteLine($"\nIf 'basePath' is just a directory name, '{basePath}' is preceeded.");
                Console.WriteLine("\nTransformConfig - Example (inside PostBuild event):");
                Console.WriteLine("\t\"c:\\Tools\\TransformConfig.exe\"  /d:Web.config /t:Web.$(ConfigurationName).config /s:Web.Template.config /b:\"$(ProjectDir)\\\"");
                Environment.ExitCode = 1;
                return 1;
            }

            foreach (var a in args)
            {
                var param = a.Trim().Substring(3).TrimStart();
                switch (a.TrimStart().Substring(0,2).ToLowerInvariant())
                {
                    case "/d":
                        tgtConfigFileName = param ?? tgtConfigFileName;
                        break;
                    case "/t":
                        transformFileName = param ?? transformFileName;
                        break;
                    case "/b":
                        var isPath = (param ?? "").Contains("\\");
                        basePath = (isPath == false)
                                    ? $@"{myVsProjects}\" + param ?? ""
                                    : param;
                        break;
                    case "/s":
                        srcConfigFileName = param ?? srcConfigFileName;
                        break;
                    default:
                        break;
                }
            }
            basePath = System.IO.Path.GetFullPath(basePath);
            if (!basePath.EndsWith("\\")) basePath += "\\";
            if (tgtConfigFileName != srcConfigFileName)
            {
                System.IO.File.Copy(basePath + srcConfigFileName,
                                     basePath + tgtConfigFileName, true);
            }
            TransformConfig(basePath + tgtConfigFileName, basePath + transformFileName);
            Console.WriteLine($"TransformConfig - transformed '{basePath + tgtConfigFileName}' successfully using '{transformFileName}'.");
            Environment.ExitCode = 0;
            return 0;
        }
        catch (Exception ex)
        {
            var msg = $"{ex.Message}\nParameters:\n/d:{tgtConfigFileName}\n/t:{transformFileName}\n/s:{srcConfigFileName}\n/b:{basePath}";
            Console.WriteLine($"TransformConfig - Exception occurred: {msg}");
            Console.WriteLine($"TransformConfig - Processing aborted.");
            Environment.ExitCode = 2;
            return 2;
        }
    }

    public static void TransformConfig(string configFileName, string transformFileName)
    {
        var document = new XmlTransformableDocument();
        document.PreserveWhitespace = true;
        document.Load(configFileName);

        var transformation = new XmlTransformation(transformFileName);
        if (!transformation.Apply(document))
        {
            throw new Exception("Transformation Failed");
        }
        document.Save(configFileName);
    }

  }
}

Asegúrese de agregar la DLL "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.XmlTransform.dll"como referencia (este ejemplo se aplica a VS 2015, para versiones anteriores, reemplace v14.0en la ruta por el número de versión apropiado, por ejemplo v11.0).

Para Visual Studio 2017, el esquema de nomenclatura para la ruta ha cambiado: Por ejemplo, para la versión de la empresa es aquí: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Microsoft\VisualStudio\v15.0\Web.
Supongo que para la versión profesional debe reemplazar Enterpriseen la ruta por Professional. Si está utilizando la versión de vista previa, reemplace adicionalmente 2017por Preview.

He aquí un resumen de cómo el camino ha cambiado para diferentes versiones de Visual Studio (si no tiene la versión de la empresa puede que tenga que sustituir Enterprisepor Professionalen el camino):

        Ruta de la Microsoft.Web.XmlTransform.dllversión VS (para )
2015                   C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\Web
2017                   C:\Program Files (x86)\Microsoft Visual Studio\2017\
                          Enterprise\MSBuild\Microsoft\VisualStudio\v15.0\Web
2019                  C:\Program Files (x86)\Microsoft Visual Studio\2019\
                          Enterprise\MSBuild\Microsoft\VisualStudio\v16.0\Web

Compílelo y coloque el archivo .exe en un directorio, por ejemplo C:\MyTools\.

Uso: puede usarlo en su evento posterior a la compilación (en las propiedades del proyecto , seleccione Eventos de compilación y luego edite la línea de comando del evento posterior a la compilación ). Los parámetros de la línea de comandos son (ejemplo):

"C: \ MyTools \ TransformConfig.Exe" /d:Web.config /t:Web.$(ConfigurationName).config /s:Web.Template.config / b: "$ (ProjectDir) \"

es decir, primero el nombre del archivo de configuración, seguido del archivo de configuración de transformación, seguido de una configuración de plantilla opcional, seguida de la ruta a su proyecto que contiene ambos archivos.

He agregado el parámetro de configuración de plantilla opcional porque, de lo contrario, la transformación sobrescribirá su configuración original completa, lo que puede evitarse proporcionando una plantilla.

Cree la plantilla simplemente copiando el Web.config original y asígnele el nombre Web.Template.config.

Nota:

  • Si lo prefiere, también puede copiar el TransformConfig.exearchivo a la ruta de Visual Studio mencionada anteriormente donde Microsoft.Web.XmlTransform.dllreside y consultarlo en todos sus proyectos donde necesita transformar sus configuraciones.

  • Para aquellos de ustedes que se preguntan por qué agregué Environment.ExitCode = x;asignaciones: simplemente devolver un int de Main no ayudó en el evento de compilación. Vea los detalles aquí.

  • Si está publicando su proyecto y está utilizando un Web.Template.config, asegúrese de haber reconstruido su solución con la configuración correcta (generalmente Release) antes de publicar. La razón es que Web.Config se sobrescribe durante la depuración y, de lo contrario, podría terminar transformando el archivo incorrecto.

Mate
fuente
1
Parece que la publicación de CodeProject está maltratada. Usó capturas de pantalla para sus ejemplos de código, y ahora que su blog está inactivo, se pierden en la historia.
Eric Lloyd
3
Sí, desafortunadamente las capturas de pantalla desaparecieron. Pero al menos el texto del artículo sigue ahí, describiendo el enfoque. He agregado la descripción del texto a mi respuesta para evitar perderla.
Matt
1
Es cierto que tal vez uno pueda intentar contactar al autor James Coleman en codeproject para arreglarlo allí. Sin embargo, no estoy seguro de si todavía está activo allí. @ThomasTeilmann
Matt
Creo que esto podría ser similar a lo que estaba en las capturas de pantalla perdidas. Parece lograr el mismo resultado básico. stackoverflow.com/a/6437192/1003916
user1003916
22

Responder a su pregunta no es simple, porque plantea un problema, si desea transformar Web.config con Web.debug.config, ¿dónde se debe almacenar el efecto de transformación? ¿En el propio Web.config? ¡Esto sobrescribiría el archivo fuente de transformación! Probablemente es por eso que Visual Studio no realiza transformaciones durante las compilaciones.

La respuesta anterior de Matt es válida, pero es posible que desee combinarlos para tener una solución genérica que funcione cuando realmente cambie la configuración de la solución activa de depuración a versión, etc. Aquí hay una solución simple:

  1. Cree sus transformaciones de configuración para configuraciones (depuración, lanzamiento, etc.)
  2. Rebautizar Web.config el archivo a Web.base.config: las transformaciones deben cambiar el nombre automáticamente en consecuencia ( Web.base.Debug.config, etc.)
  3. Agregue el siguiente archivo XML transformWebConfig.proj a la carpeta de su proyecto:
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" DefaultTargets="TransformWebConfig" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
  <Target Name="TransformWebConfig">
    <TransformXml Source="Web.base.config" Transform="Web.base.$(CurrentConfig).config" Destination="Web.config" />
  </Target>
</Project>
  1. Navegue a las propiedades de su proyecto, elija Eventos de compilación y agregue el siguiente contenido a la línea de comando del evento posterior a la compilación :
@if exist "%ProgramFiles(x86)%\MSBuild\12.0\bin" set PATH=%ProgramFiles(x86)%\MSBuild\12.0\bin;%PATH%
msbuild $(ProjectDir)transformWebConfig.proj /t:TransformWebConfig /p:CurrentConfig=$(ConfigurationName) /p:TargetProjectName=$(TargetPath)

Ahora, cuando cree su solución, se creará un archivo Web.config con transformaciones válidas para la configuración activa.

Komsky
fuente
La mejor y más limpia respuesta. Algunas preguntas: 1. ¿Por qué las validaciones XML dicen que el elemento TransformXml no es válido dentro del elemento Target? (la construcción funciona por cierto). 2. Ahora que esto genera el Web.Config real, todavía agrego el Web.Config al proyecto. Ahora, cada vez que cambio entre Debug / Release, el web.config cambiará, pero no necesariamente quiero confirmarlo todo el tiempo en el repositorio de origen.
Csaba Toth
1. No puedo decir realmente cómo VS valida este XML con el esquema, pero esta advertencia es común, por lo que puede ignorarla. 2. Depende del repositorio que esté utilizando, pero puede, por ejemplo, utilizar la entrada del archivo git.ignore.
komsky
4
Esto funcionó bien para mí, simplemente cambié el 12 en el archivo build-event y proj a la versión actual. Para el evento posterior a la generación utilicé: '"$(MSBuildBinPath)\msbuild.exe" $(ProjectDir)TransformWebConfig.proj /t:TransformWebConfig /p:CurrentConfig=$(ConfigurationName) /p:TargetProjectName=$(TargetPath) y actualizada v12.0a v14.0en el archivo .proj.
Jovie
1
Para VS 2017 modificar cada 12.0a14.0
Csaba Toth
1
1) no olvide incluir el archivo web.config generado en el proyecto web, o no se copiará en la carpeta de destino después de su publicación. 2) si el servidor de compilación no tiene estos dos archivos, simplemente cópielos en el servidor "Microsoft.Web.Publishing.Tasks", "Microsoft.Web.XmlTransform"
phiree
8

para VS 2017 encontré la respuesta aquí no estoy seguro de por qué nadie la ha mencionado anteriormente, ya que parece ser una solución muy popular. Muy fácil también. Asegúrese de ver el comentario de IOrlandoni el 5 de marzo de 2019 para que funcione en VS 2017 y todas las versiones.

Básicamente es un dos pasos. Primero, edita el archivo .csproj, agregando el código a continuación. En segundo lugar, crea una nueva configuración web.base.config y copia allí el web.config existente. Después de hacer eso, cualquier compilación sobrescribirá su web.config con la transformación deseada.

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\WebApplications\Microsoft.WebApplication.targets" />
<Target Name="BeforeBuild">
    <TransformXml Source="Web.Base.config" 
        Transform="Web.$(Configuration).config" Destination="Web.config" />
</Target>  
Tom McDonald
fuente
Esta es probablemente la mejor respuesta, pero en mi opinión, le falta un truco. Si cambia Web.configde Contenta None, puede usar Source="Web.config" Destination="$(TargetPath).config"(o quizás para algunos tipos de proyectos Destination="$(TargetDir)Web.config"). También moví la transformación a AfterBuild, ya que ya no es necesario hacerlo antes de copiar los archivos.
Peter Taylor
Ok, en realidad eso no funciona porque por alguna razón no puedo configurarlo para que se ejecute bin.
Peter Taylor
4

Su pregunta inmediata ha sido respondida: la explicación es que la transformación se aplica en la publicación, no en la compilación.

Sin embargo, creo que no ofrece una solución sobre cómo lograr lo que quieres hacer.

He estado luchando con este problema exacto durante unos días, buscando una manera de mantener limpio web.config y configurar todas las claves que varían según el entorno en los archivos de transformación respectivos. Mi conclusión es que la solución más fácil y estable es usar valores de depuración en el archivo web.config original, de esa manera siempre estarán presentes cuando se ejecuta la depuración en Visual Studio.

Luego, cree transformaciones para los diferentes entornos en los que desea publicar: prueba, integración, producción, lo que sea que tenga. La funcionalidad ahora incorporada para transformar archivos web.config en la publicación será suficiente para esto. No es necesario utilizar SlowCheetah ni editar eventos de compilación ni archivos de proyecto. Si solo tiene proyectos web, eso es.

Si lo desea, también puede tener el archivo web.debug.config en su solución, solo para mantener un archivo separado con todos los valores pertenecientes al entorno de desarrollo. Sin embargo, asegúrese de comentar que los valores no se aplican cuando se ejecuta en Visual Studio, en caso de que alguien más intente usarlo para ese propósito.

Carolina Persson
fuente
1

Use Octopus Deploy (la edición Community es gratuita) y deje que transforme el archivo web.configpor usted. Pasos:

  1. Configure Octopus para implementar su aplicación web
  2. Asegúrese de que Web.Release.configtenga la Build Actionpropiedad configurada Contentcomo su web.configarchivo principal .

¡Eso es! Octopus hará el resto sin ninguna configuración especial. Una implementación predeterminada del sitio web de IIS hará esto de inmediato:ingrese la descripción de la imagen aquí

Matt Kocaj
fuente
El número 2 es la clave :)
Reza
0

Aparentemente hay una extensión para Visual Studio 2015

https://visualstudiogallery.msdn.microsoft.com/05bb50e3-c971-4613-9379-acae2cfe6f9e

Este paquete le permite transformar su app.config o cualquier otro archivo XML basado en la configuración de compilación

Amir Astaneh
fuente
SlowCheeta no es nuevo. ha estado ahí durante mucho tiempo. Su lanzamiento 1.1 fue el 8/9/2011
HaBo
@HaBo Gracias por su atención, eliminé el newmundo como oración.
Amir Astaneh
0

Recientemente tuve el mismo problema con un archivo web.config anterior basado en .NET Framework 2.0. La solución fue simplemente eliminar el espacio de nombres de web.config ( attibute xmlns en el nodo raíz de configuración ):

ANTES DE: <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

DESPUÉS: <configuration>

Rafael Neto
fuente