Multiple level web.config transform - c#

Is there any way to apply a web.config transform on more than one level? E.g:
web.config
- web.release.config
- web.prod1.config
- web.prod2.config
When targeting prod1, I would like to do a 3 way merge web.config < web.release.config < web.prod1.config. Is this possible?

There is a way to accomplish this. As you don't specify too much, I'm not sure this will satisfy your requirements though. The following is how it could be accomplished from scratch, but you could just pull the bits that you need directly into the csproj you already have.
Create a .csproj file:
Transform.csproj
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="Web.config" />
<None Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon>
</None>
<None Include="Web.Prod.config">
<DependentUpon>Web.config</DependentUpon>
</None>
<None Include="Web.Release.config">
<DependentUpon>Web.config</DependentUpon>
</None>
</ItemGroup>
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="TransformRelease">
<TransformXml Source="Web.config"
Transform="Web.Release.config"
Destination="Web.New.config"/>
</Target>
<Target Name="TransformProd">
<TransformXml Source="Web.New.config"
Transform="Web.Prod.config"
Destination="Web.New.config"/>
</Target>
</Project>
Then you can execute your two transforms through invoking an msbuild command from the command line. I used the following powershell commands.
.\msbuild.exe "PATH_TO_YOUR_CSPROJ\Transform.csproj" /t:TransformRelease
.\msbuild.exe "PATH_TO_YOUR_CSPROJ\Transform.csproj" /t:TransformProd
This will transform your web.config using the transforms in the web.release.config and create a new file with the result of that transform web.new.config. Then the second command will transform the web.new.config using the transforms in web.prod.config and update the web.new.config with that transformed value.
Web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="web" value="web" />
<add key="release" value="web" />
<add key="prod" value="web" />
<add key="release:prod" value="web" />
</appSettings>
</configuration>
Web.Release.config
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="release" value="release" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
<add key="release:prod" value="release" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
</appSettings>
</configuration>
Web.Prod.config
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="prod" value="prod" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
<add key="release:prod" value="prod" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
</appSettings>
</configuration>
Running the above commands produced Web.New.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="web" value="web" />
<add key="release" value="release" />
<add key="prod" value="prod" />
<add key="release:prod" value="prod" />
</appSettings>
</configuration>
UPDATE
While the above works, I wouldn't want to use it in that manner. After tinkering around with the .csproj a bit, I came up with this which will do the transformation for you in the BeforeBuild task.
<Target Name="TransformRelease">
<TransformXml Source="Web.config" Transform="Web.Release.config" Destination="Web.New.config" />
</Target>
<Target Name="TransformProd" Condition="'$(Configuration)' == 'Release'">
<TransformXml Source="Web.New.config" Transform="Web.Prod.config" Destination="Web.New.config" />
</Target>
<Target Name="BeforeBuild">
<MSBuild Projects="WebApplication1.csproj" Targets="TransformRelease;TransformProd"/>
</Target>
With these defined in your .csproj file, when you build the project as is, it will apply the Release transform alone. When you build the project in the Release configuration, it will apply both the Release and Prod transformations. Obviously you will need to tweak it for your needs given prod1, prod2, etc.

Is not possible out of the box with simple commands, but you can do custom transformation and string replacement using build tasks
A while ago I asked a similar questions and I got a really nice answer using build tasks transformation. Instead of copying it here, take a look in the solution and adapt to your needs.:
Service Fabric Default Publish Profile other than Local.xml

Related

Configuring Specflow+XUnit Tests to run on multiple environments

I have to migrate my existing tests from Specflow+Runner to Specflow+xUnit and for the same I'have been stuck on converting the .runsettings and .srprofile files so that they can be used with Specflow+xUnit.
Has anyone done this conversion or can help me with this ?
Existing .runsettings file :
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- Configurations for SpecRun -->
<SpecRun>
<Profile>DEV.srprofile</Profile>
<!-- SpecRun uses VisualStudo.srprofile or TFS.srprofile by default, or the Default.srprofile if these don't exist.
<Profile>MyProfile.srprofile</Profile> -->
<!-- SpecRun generates a report file based on the project name and the current timestamp by default.
<ReportFile>CustomReport.html</ReportFile>-->
<GenerateSpecRunTrait>false</GenerateSpecRunTrait> <!-- Set this to "true" to generate a "SpecRun" trait for all tests discovered by SpecRun (useful when the solution contains other tests too) -->
<GenerateFeatureTrait>false</GenerateFeatureTrait> <!-- Set this to "true" to generate a feature trait for all scenarios (othervise the feature goruping can be used as "class") -->
</SpecRun>
</RunSettings>
Existing .srprofile file :
<?xml version="1.0" encoding="utf-8"?>
<TestProfile xmlns="http://www.specflow.org/schemas/plus/TestProfile/1.5">
<Settings projectName="example" />
<Execution stopAfterFailures="3" testThreadCount="1" testSchedulingMode="Sequential" />
<!-- For collecting by a SpecRun server update and enable the following element. For using the
collected statistics, set testSchedulingMode="Adaptive" attribute on the <Execution> element.
<Server serverUrl="http://specrunserver:6365" publishResults="true" />
-->
<TestAssemblyPaths>
<TestAssemblyPath>example.dll</TestAssemblyPath>
</TestAssemblyPaths>
<DeploymentTransformation>
<Steps>
<ConfigFileTransformation configFile="App.config">
<Transformation>
<![CDATA[<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="baseUrl" value="<sample URL>" xdt:Locator="Match(key)" xdt:Transform="SetAttributes" />
<add key="shortWait" value="15" xdt:Locator="Match(key)" xdt:Transform="SetAttributes" />
<add key="longWait" value="25" xdt:Locator="Match(key)" xdt:Transform="SetAttributes" />
</appSettings>
</configuration>
]]>
</Transformation>
</ConfigFileTransformation>
</Steps>
</DeploymentTransformation>
</TestProfile>

Compiling a .Net Core Console App with CoreRT and Dapper

I'm attempting to compile my code (a .net core console application) into a native .exe for win-x64 using CoreRT. I was able to follow the documentation right up until the section having to do with reflection and using an rd.xml file, which is where I am currently stuck.
My project uses Dapper as the ORM, which relies on reflection to bind objects from my database. I only have 2 different types that I am binding, so my assumption is that I need to include these types in the rd.xml.
Right now, when I try to run dotnet publish -r win-x64 -c release from the .net core cli, it finishes successfully, however at runtime, my compiled .exe throws an exception with the following snippet:
---> (Inner Exception #0) System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the
InnerException's StackTrace property. --->
EETypeRva:0x01202268(System.Reflection.MissingRuntimeArtifactException):
This object cannot be invoked because it was metadata-enabled for
browsing only:
'Dapper.SqlMapper.TypeHandlerCache<System.Data.DataTable>.SetHandler(Dapper.SqlMapper.ITypeHandler)'
For more information, please visit
http://go.microsoft.com/fwlink/?LinkID=616867
My rd.xml file looks like this:
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<Type Name="Dapper.SqlMapper.TypeHandlerCache{System.Data.DataTable}">
<MethodInstantiation Name="SetHandler" Arguments="Dapper.SqlMapper.ITypeHandler" Dynamic="Required" />
</Type>
</Application>
</Directives>
I assume I need to include references to my models here which would be Foo and Bar, but the error getting thrown refers to Dapper specifically.
The rd.xml file is inside of my project directory, and is referenced in the MyProject.csproj like so:
<ItemGroup>
<EmbeddedResource Include="rd.xml" />
</ItemGroup>
I'm wondering if this problem is due to my structure (perhaps the rd.xml should be referenced in a different way), or due to the content of my rd.xml file. Has anyone dealt with this, or used CoreRT on a project that uses Dapper?
Try add this to directives:
<Assembly Name="System.Data.Common">
<Type Name="System.Data.DataTable" Dynamic="Required All"/>
</Assembly>
To compile my project successfully I had to do the following:
Include all references in the System.Data.SqlClient nuget package as project references. In this case I also included those of System.Configuration.ConfigurationManager.
From the command line publish for the required operating system: dotnet publish -r win-x64
From publishing path copy the System.Data.SqlClient.dll and sni.dll files to a fixed path: ..\SQLClient\win-x64\
In csproj file make the conditional reference to the nuget package and to published dll, in this case through the parameter MSBuild NativeCompilation.
Finally, publish with CoreRT from the command line: dotnet publish -r win-x64 / p: NativeCompilation = true
And it worked!
GetIPVersionSQLSRV.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup Condition="'$(BuildingInsideVisualStudio)' == 'true' OR '$(NativeCompilation)' != 'true'">
<!--System.Data.SqlClient Nuget Reference -->
<PackageReference Include="System.Data.SqlClient" Version="4.6.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0" />
<!--System.Data.SqlClient References-->
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
<PackageReference Include="System.Security.AccessControl" Version="4.5.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
<!--System.Configuration.ConfigurationManager References-->
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.5.0" />
<PackageReference Include="System.Security.Permissions" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(BuildingInsideVisualStudio)' != 'true' AND '$(NativeCompilation)'=='true'">
<!-- ILCompiler and rd.xml -->
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="1.0.0-alpha-*" />
<RdXmlFile Include="rd.xml" />
<!--System.Data.SqlClient published Dll Reference -->
<Reference Include="System.Data.SqlClient">
<HintPath>..\SQLClient\win-x64\System.Data.SqlClient.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Program.cs
using System;
using System.Configuration;
using System.Data.SqlClient;
using System.IO;
using System.Net;
namespace GetIPVersionSQLSRV
{
class Program
{
private static String config = ConfigurationManager.AppSettings["texto"];
private static String cadena = ConfigurationManager.ConnectionStrings["default"].ConnectionString;
static void Main(string[] args)
{
Console.WriteLine("Hello World! " + config);
using(SqlConnection conn = new SqlConnection(cadena))
{
conn.Open();
using (SqlCommand comm = new SqlCommand("SELECT ##VERSION;", conn))
Console.WriteLine(comm.ExecuteScalar());
}
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(#"http://api.ipify.org?format=json");
using (HttpWebResponse res = (HttpWebResponse)req.GetResponse())
using (Stream strm = res.GetResponseStream())
using (StreamReader read = new StreamReader(strm))
Console.WriteLine(read.ReadToEnd());
req = null;
Console.ReadKey(false);
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="texto" value="CONFIG2"/>
</appSettings>
<connectionStrings>
<add name="default" connectionString="User ID=user;PWD=p455w0rd;Initial Catalog=master;Data Source=localhost"/>
</connectionStrings>
</configuration>
rd.xml
<Directives>
<Application>
<Assembly Name="System.Configuration.ConfigurationManager">
<Type Name="System.Configuration.ClientConfigurationHost" Dynamic="Required All"/>
<Type Name="System.Configuration.AppSettingsSection" Dynamic="Required All"/>
</Assembly>
</Application>
</Directives>
nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<add key="dotnet-core"
value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"/>
<add key="nuget.org"
value="https://api.nuget.org/v3/index.json" protocolVersion="3"/>
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

How to configure Obfuscar, The Open Source Obfuscation Tool

I m trying to use the obfuscar free tool to protect my code from reverse engineering. I'm trying to obfuscate the provided example Basic Example. The problem that I can't find how to configure it.
Here's my config.xml
<configuration>
<startup><supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.0,Profile=Client"/>
</startup>
<Obfuscator>
<Var name="InPath" value="C:\Users\user\Desktop\Obfuscar_2.0.0\Examples\BasicExample\BasicExampleExe\bin\Debug" />
<Var name="OutPath" value="C:\Users\user\Desktop\Obfuscar_2.0.0\Examples\BasicExample\BasicExampleExe\bin\Debug" />
<Module file="$(InPath)\BasicExampleExe.exe" />
<Module file="$(InPath)\BasicExampleLibrary.dll" />
<Var name="KeepPublicApi" value="true" />
<Var name="HidePrivateApi" value="true" />
</Obfuscator>
</configuration>
I had the same question... the example Release.proj build file they provide in the Git attempts to build the Obfuscar binary from scratch. So you need to change two lines of the Release.proj file, assuming you're going to be using the pre-compiled Obfuscar binary file.
First, change the path to the ObfuscarExe path to where you have the binary saved similar to this:
<!-- obfuscator bits -->
<PropertyGroup>
<ObfuscatorExe>C:\Program Files (x86)\Obfuscar\obfuscar.Console.exe</ObfuscatorExe>
<ObfuscatorProject>$(BasePath)\obfuscar.xml</ObfuscatorProject>
<ObfuscatorInput>$(BasePath)\Obfuscator_Input</ObfuscatorInput>
<ObfuscatorOutput>$(BasePath)\Obfuscator_Output</ObfuscatorOutput>
</PropertyGroup>
Second, comment out the command to compile the Obfuscar solution:
<ItemGroup>
<CompileSolution Include="$(BasePath)\BasicExample.sln" />
<!-- <CompileObfuscar Include="..\..\Obfuscar\Obfuscar.sln" /> -->
</ItemGroup>

MSBuildProjectDirectory not resolving for MSBuild.ExtensionPack

I'm working on an ASP.NET 4 WebAPI project and am including a wpp.targets file. I need to use MSBuild.ExtensionPack.Xml.XmlFile to replace a value in one of my configuration XML files.
The problem is that I don't want to install MSBuild.ExtensionPack on all the machines so I packaged it up with the project. On my local build, the path to the MSBuild.ExtensionPack.dll resolves correctly. On my build machine though, I keep getting this error: The "MSBuild.ExtensionPack.Xml.XmlFile" task could not be loaded from the assembly C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.dll.
It seems to be resolving to the default install location of the package.
Here's what's in my wpp.targets file:
<?xml version="1.0" encoding="utf-8"?>
<!-- Sets the assembly which will run the transformation on Web.config (Should be installed on Dev machines) -->
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<!-- Get the path to the MSBuild.Extension.Pack -->
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks')">$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<!--Import the MSBuild.Extension.Pack package -->
<Import Project="$(TPath)"/>
<!-- Make sure web.config and transformation files exist -->
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.config')" />
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.$(Configuration).config')" />
<!-- Make sure web.config will be there even for package/publish -->
<Target Name="CopyWebConfig" BeforeTargets="Build;Rebuild">
<Copy SourceFiles="Web.Base.config"
DestinationFiles="Web.config"
OverwriteReadOnlyFiles="true"
SkipUnchangedFiles="false" />
</Target>
<!-- Run Web.Config transformation on a build as well (not just a publish) -->
<Target Name="CustomTransformWebConfigOnBuild" AfterTargets="CopyWebConfig" >
<Message Text="Transforming: Web.$(Configuration).config" Importance="high" />
<TransformXml Source="Web.Base.config"
Transform="Web.$(Configuration).config"
Destination="Web.config" />
</Target>
<!-- Update Web.Config's config attribute -->
<Target Name="UpdateConfigAttribute" AfterTargets="CustomTransformWebConfigOnBuild" Condition="$(Configuration) != 'Release'">
<Message Text="Transforming: Web.config" Importance="high" />
<MSBuild.ExtensionPack.Xml.XmlFile TaskAction="UpdateAttribute"
File="Web.config"
XPath="/configuration/appSettings/add[#key='config_url']"
Key="value"
Value="www.randomurl.com"/>
</Target>
When you have more than 1 option for the source location for a helper-dll...I like to do it like with the style below.
Something like this (Obviously, you'll have to put real locations in for "PossibleLocationOne" and "PossibleLocationTwo").
<PropertyGroup>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationOne\MSBuild.ExtensionPack.dll')">..\PossibleLocationOne\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationTwo\MSBuild.ExtensionPack.dll')">..\PossibleLocationTwo\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<!--Now check to see if either of the two above resolved , if not , add something to the path so you can at least -->
<MyFoundMSBuildExtensionPackLocation Condition="'$(MyFoundMSBuildExtensionPackLocation)'==''">DID_NOT_FIND_A_PATH_FOR_MSBUILDEXENSIONPACK\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
</PropertyGroup>
<UsingTask AssemblyFile="$(MyFoundMSBuildExtensionPackLocation)" TaskName="TransformXml"/>
Add all options for possible locations..and one extra for "I didn't find a match"....
Then use the "UsingTask".
"UsingTask" is ~~after~~ the MyFoundMSBuildExtensionPackLocation(PropertyGroup).......so the $(MyFoundMSBuildExtensionPackLocation) resolves before the UsingTask is called.

Web.config transform moving namespace declaration

Using this online tester it is easy to see the following issue
I have a web.config that looks like:
<?xml version="1.0"?>
<configuration>
<nlog/>
</configuration>
And a transform that looks like:
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<xdt:Import assembly="AppHarbor.TransformTester" namespace="AppHarbor.TransformTester.Transforms"/>
<nlog xdt:Transform="Replace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true">
<target name="LogMill" xsi:type="FallbackGroup" returnToFirstOnSuccess="true">
<target xsi:type="LogMillMessageBus"/>
<target xsi:type="File" fileName="..\LogMill-FailSafe.log" layout="${TextErrorLayout}"/>
</target>
</targets>
</nlog>
</configuration>
But the output is not what I expect, it moves the xsi namespace declaration down to the element that uses it, which causes nlog to fail to parse the configuration with the error Parameter p4 not supported on FallbackGroupTarget
<?xml version="1.0"?>
<configuration>
<nlog>
<targets async="true">
<target name="LogMill" p4:type="FallbackGroup" returnToFirstOnSuccess="true" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
<target p4:type="LogMillMessageBus" /><target p4:type="File" fileName="..\LogMill-FailSafe.log" layout="${TextErrorLayout}" />
</target>
</targets>
</nlog>
</configuration>
Is there a transform option or syntax that I can apply to prevent it from moving the namespace declaration? I couldn't find anything in the documentation
Move your xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" declaration to the topmost element and it should be OK
In my case instead of
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
I had to use
xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"

Categories

Resources