Use app.config transformation in WPF projects with ClickOnce publishing

Thanks to this great post of João Angelo, I finally found a solution to the issue of using app.config transformation in WPF projects with ClickOnce publishing.

Following are the steps I implemented to solve it:

  1. Right click on the WPF project and select Unload Project
  2. Right click on the unloaded project and select Edit projectname.csproj
  3. Add the following lines at the beginning of the file (just under the Project declaration):

    <!-- The transformation target (TransformWebConfig) in this targets file—>  

    <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets" />  

    <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

  4. Add the following lines at the end of the document (just before the Project tag closing):

      <PropertyGroup>    

    <!-- Prevent circular dependency on Build target –>    

    <PipelineDependsOnBuild>false</PipelineDependsOnBuild>    

    <!-- Override project config file name (By default is set to Web.config) –>    

    <ProjectConfigFileName>App.config</ProjectConfigFileName>  

    </PropertyGroup>  

    <!-- Removes the need to set config files Build Action as Content –>  

    <ItemGroup>    

    <FilesForPackagingFromProject Include="$(ProjectConfigFileName)">      

    <DestinationRelativePath>$(ProjectConfigFileName)</DestinationRelativePath>    

    </FilesForPackagingFromProject>  

    </ItemGroup>  

    <!-- Insert transformation targets in the build process –>  

    <PropertyGroup>    

    <BuildDependsOn> TransformWebConfig; OverrideAppConfigWithTargetPath; $(BuildDependsOn); CopyTransformedConfig </BuildDependsOn>  

    </PropertyGroup>  

    <PropertyGroup>    

    <TransformedConfig>$(TransformWebConfigIntermediateLocation)\transformed\App.config</TransformedConfig>  

    </PropertyGroup>  

    <!-- Overrides AppConfigWithTargetPath allowing the transformed config to be used for manifest generation –>  

    <Target Name="OverrideAppConfigWithTargetPath">    

    <ItemGroup>      

    <AppConfigWithTargetPath Remove="@(AppConfigWithTargetPath)" />      

    <AppConfigWithTargetPath Include="$(TransformedConfig)" Condition="'$(TransformedConfig)'!=''">        

    <TargetPath>$(TargetFileName).config</TargetPath>      

    </AppConfigWithTargetPath>    

    </ItemGroup>  

    </Target>  

    <!-- Copy transformed file to output directory –>  

    <Target Name="CopyTransformedConfig" Condition="'$(TargetName)' != ''">    

    <Copy Condition="Exists('$(TransformedConfig)')" SourceFiles="$(TransformedConfig)" DestinationFiles="$(OutputPath)$(TargetName)$(TargetExt).config" />    

    <Copy Condition="Exists('$(TransformedConfig)') And '$(TargetExt)' == '.exe'" SourceFiles="$(TransformedConfig)" DestinationFiles="$(OutputPath)$(TargetName).vshost.exe.config" />  

    </Target>  

    <!--   Override After Publish to support ClickOnce      AfterPublish target replaces the untransformed config file copied to the   deployment directory with the transformed one   -->  

    <Target Name="AfterPublish">    

    <PropertyGroup>      

    <DeployedConfig>$(_DeploymentApplicationDir)$(TargetName)$(TargetExt).config$(_DeploymentFileMappingExtension)</DeployedConfig>    

    </PropertyGroup>    

    <!-- Publish copies the unstransformed App.config to deployment directory so overwrite it –>    

    <Copy Condition="Exists('$(DeployedConfig)')" SourceFiles="$(TransformedConfig)" DestinationFiles="$(DeployedConfig)" />  

    </Target>

  5. Use the following lines to view App.Debug.config and App.Release.config under the default App.config file, as a subtree, in VS IDE:
     <None Include="App.Debug.config">
          <DependentUpon>App.config</DependentUpon>
        </None>
        <None Include="App.Release.config">
          <DependentUpon>App.config</DependentUpon>
        </None>
  6. Save the project and Reload the project.
  7. Try it changing the Build configuration and publishing the solution!
Advertisements

5 thoughts on “Use app.config transformation in WPF projects with ClickOnce publishing

  1. Hi,

    When you trying to debug project, if project have edmx file, application throws “Unable to load the specified metadata resource.” Exception. I check connection string,everything seems normal. But when remove your lines from project file, everything start working.

    • Hi Deniz,
      I’ve just tried to add an edmx file and everything seems right: I can debug without any problem.
      Are you able to debug without edmx file?
      Can you post your app.config file?

      • Hi Again,

        I compare two app.config, before and after adding transformation commands to csproj. Configuration files are same. I think transformation commands changes something else.

        Here is App.Config, App.Debug.Config, App.Release.Config ;

        ———- App.Config ———–

        ……..
        ………
        …….

        ——– App.Debug.Config ————

        ……..
        ………
        …….

        ——— App.Release.Config ———–

        ……..
        ………
        …….

        ———————————————–

        Here is detailed Exception ;

        TypeInitializationException ;

        Message : The type initializer for ‘WinUI.Program’ threw an exception
        StackTrace : at WinUI.Program.Main()
        at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
        at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
        at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
        at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
        at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
        at System.Threading.ThreadHelper.ThreadStart()

        —————–> Inner Exception : System.Data.MetadataException ;

        Message : Unable to load the specified metadata resource.
        Stack Trace : at System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.LoadResources(String assemblyName, String resourceName, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
        at System.Data.Metadata.Edm.MetadataArtifactLoaderCompositeResource.CreateResourceLoader(String path, ExtensionCheck extensionCheck, String validExtension, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
        at System.Data.Metadata.Edm.MetadataArtifactLoader.Create(String path, ExtensionCheck extensionCheck, String validExtension, ICollection`1 uriRegistry, MetadataArtifactAssemblyResolver resolver)
        at System.Data.Metadata.Edm.MetadataCache.SplitPaths(String paths)
        at System.Data.Common.Utils.Memoizer`2.c__DisplayClass2.b__0()
        at System.Data.Common.Utils.Memoizer`2.Result.GetValue()
        at System.Data.Common.Utils.Memoizer`2.Evaluate(TArg arg)
        at System.Data.EntityClient.EntityConnection.GetMetadataWorkspace(Boolean initializeAllCollections)
        at System.Data.Objects.ObjectContext.RetrieveMetadataWorkspaceFromConnection()
        at System.Data.Objects.ObjectContext..ctor(EntityConnection connection, Boolean isConnectionConstructor)
        at System.Data.Objects.ObjectContext..ctor(String connectionString, String defaultContainerName)
        at WinUI.Data.DamageTrackingEntities..ctor() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Data\EntityBase1.Designer.cs:line 52
        at WinUI.Repositories.MainMenuRepository.GetAllMainMenu() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Repositories\MainMenuRepository.cs:line 13
        at WinUI.Presenters.MainMenuPresenter.FillMenuItems() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Presenters\MainMenuPresenter.cs:line 24
        at WinUI.Forms.MainMenu.MainMenuForm._InitializeView() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Forms\Login\MainMenu\MainMenuForm.cs:line 57
        at WinUI.Forms.MainMenu.MainMenuForm..ctor() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Forms\Login\MainMenu\MainMenuForm.cs:line 51
        at WinUI.Program..cctor() in G:\Projects\9 Agustos 2011 Hasar Takip Programı\SourceCode\2010\WinUI\Program.cs:line 20

        Thank You,

        Deniz

      • ---------- App.Config -----------
        
        <?xml version="1.0" encoding="utf-8"?>
        <configuration>
          <configSections>
          </configSections>
          <appSettings>
                ........
                .........
                .......
          </appSettings>
        
          <connectionStrings>
            <add name="DamageTrackingEntities" connectionString="metadata=res://*/Data.EntityBase.csdl|res://*/Data.EntityBase.ssdl|res://*/Data.EntityBase.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=DamageTracking;user id=sa;password=Aa123456;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient"/>
          </connectionStrings>
        </configuration>
        
        -------- App.Debug.Config ------------
        
        <?xml version="1.0" encoding="utf-8"?>
        <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
           <appSettings>
                ........
                .........
                .......
          </appSettings>
        
          <connectionStrings>
            <add name="DamageTrackingEntities" connectionString="metadata=res://*/Data.EntityBase.csdl|res://*/Data.EntityBase.ssdl|res://*/Data.EntityBase.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=DamageTracking;user id=sa;password=Aa123456;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" xdt:Transform="Insert"/>
          </connectionStrings>
        
          <system.web>
          </system.web>
        </configuration>
        
        --------- App.Release.Config -----------
        
        <?xml version="1.0" encoding="utf-8"?>
        <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
           <appSettings>
                ........
                .........
                .......
          </appSettings>
        
          <connectionStrings>
            <add name="DamageTrackingEntities" connectionString="metadata=res://*/Data.EntityBase.csdl|res://*/Data.EntityBase.ssdl|res://*/Data.EntityBase.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=192.168.145.45;initial catalog=DamageTracking;user id=sa;password=Aa123456;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" xdt:Transform="Insert"/>
          </connectionStrings>
        
          <system.web>
          </system.web>
        </configuration>
        
        -----------------------------------------------
        
  2. Hi,

    I think i find the problem. But i dont know how can i resolve. When i remove transformation commands from csproj, and dissable with ILSpy, i see in resourceses csdl, msl, ssdl files. But when i add transformation scripts and dissable the csdl, msl, ssdl files are gone.

    By the way, my application is not WPF, it is Windows Form Application.

    Thanks for your help,

    Deniz

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s