10

Let us say i am creating an app called ConsoleApp2.

Because of some third party libraries i am using, my default app.config file is generating code like

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

That is because my solution references different versions of one library, so it needs to tell everyone: "Hey, if you look for any oldVersion of this library, just use newVersion". And that is all right.

The problem is that i want to define a separate config file "test.exe.config" where i have some settings and get rid of the automatically generated one.

In order to tell my App about the new config file i am using code like

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

And that works (almost) perfectly. And i wrote there "almost" since although the <appSettings> section is being read correctly, the <runtime> section is not being looked at in my custom config file, but the App looks for it in the default config file instead, which is a problem since i want to be able to delete that one later.

So, how can i tell my Application to read also the <runtime> information from my custom config file?


How to reproduce the issue

A simple sample to reproduce my issue is as follows:

Create a library called ClassLibrary2 (.Net Framework v4.6) with a single class as follows

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; }
    }
}

Note the reference to Newtonsoft package. The one installed in the library is v10.0.2.

Now create a Console Application called ConsoleApp2 (.Net Framework v4.6) with a class called Program which content is simply as follows:

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();

        }
    }
}

This Application should have installed also Newtonsoft, but in a different version v12.0.3.

Build the Application in Debug mode. Then, in the folder ConsoleApp2/ConsoleApp2/bin/Debug create a file called test.exe.config with following content

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

and note that in that same Debug folder there is also the default config file ConsoleApp2.exe.config with a content like

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

If a this point you run the application it will compile with no problems and you should see a Console like

enter image description here

Note that the (3) settings were read from my custom config file correctly. So far so good...

Now rename the default config file to something like _ConsoleApp2.exe.config and run again the application. You should now get a FileLoadException.

enter image description here

So again, how can i tell my Application to read the <runtime> information from my custom config file?


Rationale

The reason i am looking an answer to this question is as follows:

When we release our application, we put all the .exe and .dll files in one folder and our custom config file (with settings, etc) in another, where our clients have similar files.

In the folder with the .exe and .dll files we try to keep as little as possible so i was asked to find a way to get rid of that ConsoleApp2.exe.config if possible. Now, since the aforementioned bindings were written in that config file, i just tried moving that information to our custom config file... but so far i have failed to achieve: the binding redirects are always tried to be read from that ConsoleApp2.exe.config, so as soon as i remove it i get exceptions...

deczaloth
  • 7,094
  • 4
  • 24
  • 59
  • 1
    That is not possible. The .config is applied before the primary appdomain is created by the CLR host. Very early, even before the CLR is started and anything else is done, after which the config is locked-in. Technically you could create your own AppDomain, except that there is little future for such a solution since .NETCore and the upcoming .NET 5 no longer support them. Custom-hosting is not exactly ideal either. It is not obvious why such a hack is necessary, binding redirects are a mere deployment detail. – Hans Passant Dec 14 '19 at 21:20
  • Hi @HansPassant, thanks for your information. Do you know of anywhere in the MSDN docu where that is documented? Also, if you post it as an answer i will be glad to accept it and give you the bounty. – deczaloth Dec 14 '19 at 21:24
  • 2
    Books, Steven Pratschner has written one that gets to the nitty-gritty. Hard to recommend, the CLR changed too much since. "That is not possible" answers are not considered useful by anyone that visits SO. There is no obvious way to get you somewhere since you did not explain why you need to do this. – Hans Passant Dec 14 '19 at 21:32
  • @HansPassant, i edited my question adding the "rationale" at the end. – deczaloth Dec 14 '19 at 21:42
  • 2
    @Deczaloth I have a feeling that we have [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) - you are trying to find a solution to apply `` section from another config, but issue occurs because of the way you manage common configuration. If you manage common config in a different way, this issue is not relevant anymore. Please check my answer and consider to use configuration transforms instead of tweaking runtime. – fenixil Dec 14 '19 at 23:04
  • @fenixil, i get your point regarding XY problem. The thing is: my question is focused on finding out if an approach i visualized is doable or not. Yet, if someone comes up with a solution that does exactly what i want in a sensible way i am open to accept it (you can find the motivation of my question in the rationale section, at the end) – deczaloth Dec 16 '19 at 07:52
  • @Deczaloth, there is another answer with Config transforms from @Matt. `solution that does exactly what i want` is potentially not a good and right solution. Please step back and reconsider how do you manage your configuration and why are you doing it this way (separate file, common location, no native config in app folder). – fenixil Dec 18 '19 at 05:02

3 Answers3

6

You're probably looking for config transforms:

The idea behind is that you create multiple configurations in Visual Studio like Debug, Release, Production, Test ... in the configuration manager and a default configuration file plus so-called transforms.

Note that you can create as many configuation as you like in the configuration manager. To add new ones, click on Solution Configurations (the dropdown showing "Debug" or "Release"), and select "Configuration Manager...". Open it, and you see a list of all currently existing configurations. Drop down the combobox "Active Solution configuration" and select "<New...>" to add more.

Those transforms specify what makes the specific configuration different from the default one - so you don't need to repeat what you have already specified in the default configuration, instead you just mention the differences, for example:

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

which finds the relevant setting by its key ClientSessionTimeoutand sets its value to 100 by replacing the original value in the config file (this is what the additional transform attributes xdt:Transform="SetAttributes" xdt:Locator="Match(key)" mean). You can also specify to remove existing settings (by specifying xdt:Transform="Remove" instead), e.g.

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

would remove a user Id that should be there only for debugging, not for the release (To find out more about available options, please look here - described for Web.config, but also applicable for App.config).

In addition to the App.Config file you have one file per configuration, i.e. App.Debug.Config for Debug, App.Release.Config for Release etc. Visual Studio helps you creating them.

I have already created answers in StackOverflow here and there, which describes it in detail, please have a look.

If you're having issues displaying them in Visual Studio, take a look here.


Regarding your Rationale:

Transforms are creating a full configuration file by applying the transform file onto the default configuration file. The resulting file is compiled and put into the "bin" folder - together with the other compiled files. So, if you have a configuration "Release" selected, then all the files including the transformed config file are compiled into "bin\Release".

And the configuration file is named just as the exe file plus ".config" appended at the end (in other words, there is no ".Release.config" in the binary folder, but a "MySuperCoolApp.exe.config" created - for the application "MySuperCoolApp.exe").

Likewise, the same is true for the other configuration - each configuration creates a subfolder inside of "bin" - if you're using scripts, that subfolder can be referenced as $(TargetDir) in a post-build event.

Matt
  • 25,467
  • 18
  • 120
  • 187
  • Hi Matt, thanks for taking your time to try to help me. As you can see in an answer below, @fenixil suggested also config transforms. Nevertheless, and even though i find it a fascinating tool (i will definitely try it out in our projects!) i can not see how this solves my question. Could you extend my super-simple-sample to implement config transforms in a way that it solves my issue? (keep in mind my "rationale" at the end of my question) – deczaloth Dec 17 '19 at 10:37
  • @Deczaloth - added some hints for your Rationale, hope this makes it more clear. – Matt Dec 17 '19 at 13:02
4

Config transformation

Given the issue occur when you try to use another (non-native) config file you are trying to find a solution to 'properly' substitute it. In my answer I want to step back a little and focus on the reason why do you want to substitute it. Based on what you described in the question, you have it to define custom application settings. If I understood correctly, you plan to link it to the target project, set 'Copy to output' property to 'Always' and you'll get it near the application.

Instead of copying the new config file, there is a way to transform existing (native) one, in your case - ConsoleApp2.exe.config using Xdt transformations. To achieve that, you create transformation file and declare there only the sections which you want to transform, for example:

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

Benefits of such approach are:

  • flexibility: transforms are very flexible, you may replace sections, merge them, set/remove attributes, etc. You may have environment specific (DEV/UAT/PROD) or build specific(Debug/Release) transforms.
  • reusability: define transform once and reuse it in all projects you need.
  • granularity: you declare only what you need, no need to copy-paste whole config.
  • safety: you let nuget and msbuild manage 'native' config file (add binding redirects, etc)

The only disadvantage of this approach is learning curve: you need to learn syntax and know how to glue transforms to your configs in MSBuild.

.NET Core has support of transform, here is an example how to create transforms for web.config, but you can apply transforms to any configs.

If you develop .NET applications (not .NET Core) then I'd recommend to look at Slowcheetah.

There are a lot of resources and useful blogbosts about transformation, it is rather widely used. Please contact me if you'll have difficulties.

From my point of view config transforms is a right solution to achieve your goal so I'd strongly recommend to consider it instead of tweaking runtime.

Externalize config sections

If you still want to keep appSettings in common location, then you can externalize config sections with ConfigSource attribute. Check this and this thread for details:

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

AppSettings section contains File attribute that allows you to merge parameters from another file.

This option allows you to replace certain sections of the configuration, but not whole content itself. So if you need only appSettings, it is totally applicable - you just put config file with appSettings to common location shared with user and patch config file (add file or configSource attribute) to source this section from that location. If you need more sections, you'll need to extract them as separate files.

fenixil
  • 2,106
  • 7
  • 13
  • Hey! thanks for taking your time to read/answer my question. I will try your proposal as soon as i get back in my office :) – deczaloth Dec 14 '19 at 23:14
  • I took a look at config transformations. It is a very interesting tool, i should admit. Nevertheless i do not see how this answers my question. If i understood right you propose that i add yet an extra file (namely the config transformation file). And even leaving that aside, if a write a config transform file so that the section is automatically written in my custom config file, i still do not know how to tell the App to read the binding redirects from there instead of from the default config file. If there is something i seem not to have understood, please let me know. – deczaloth Dec 16 '19 at 07:46
  • 1
    The idea behind config transform is that you don't have any custom config file anymore and this eliminates a necessity to read runtime config from another file. Just put your app settings (or anything else) with trasforms to native config file. Does it make sense? – fenixil Dec 16 '19 at 13:21
  • I am really sorry to say this, but i still do not see the point. Could you maybe use my super-simple-sample above to produce a complete example of your proposal? Then i (hope i) will see crystal clear how Config Transform can solve my problem :) – deczaloth Dec 17 '19 at 08:26
  • it totally depends on what to consider a problem: from your perspective it is inability to replace config file in runtime so that section is consumed from another file. I'm trying to reframe your concern - if you don't need to replace config file in runtime then you don't have this problem. So problem from my perspective is how you manage configuration so that you need to do these hacks with runtime substitution. – fenixil Dec 18 '19 at 04:36
  • Thing is: if i reframe my concern then i might well just stay as i am (without the trouble of going into the learning curve problem that config transforms involve) since you see in my current state i have the default/native config file from where the section is being (correctly!) read and my custom config file in "common location", that is: native (app folder) + custom (common location) config files. On applying your solution i am not winning anything that i did not already have. Or i might be wrong... If that is the case i beg you let me know :/ – deczaloth Dec 18 '19 at 08:10
  • Unless you keep config in common location then yes and runtime configuration file change, then yes, no reason to use transforms. You probably could make your native config file cleaner to do not confuse operators if someone will look at it but this is nice to have and not critical. On the other hand runtime config file replacement looks like a hack to me, something else might not work in the future that's why I'd recommend to get rid of it and in this case you need to avoid configuration duplication in your projects and here transforms will help you a lot. Why you have this common config file? – fenixil Dec 19 '19 at 03:48
3

To work properly with different .config file, you may keep the default one to manage biding redirects and an other one for your application parameters. To do so Change default app.config at runtime looks great.

You also can shut down the automatic binding redirect generation and use only a hand crafted app.config file. There is an example here : Need a way to reference 2 different versions of the same 3rd party DLL

Edit Taking account of the rationale: If I understand it, you don't want the app.exe.config file at all. You already manage to put and read custom contant somewhere else.

Only remains the binding redirect.

You can get rid of it by managing the binding redirect at runtime like it's done here: https://stackoverflow.com/a/32698357/361177 You may also recreate a configurable binding resolver by made your code look at the config file.

My two cents here: it's feasible but I don't think it worth it.

Edit 2 This solution looks promising https://stackoverflow.com/a/28500477/361177

Orace
  • 7,822
  • 30
  • 45
  • Hey, thanks for your answer! I upvoted because of the reference to the answer from @BryanSlatner, never the less, that workaround is not exactly what i was asking for. And it might be that what i want to achieve, namely: be able to tell my application to read the binding redirects from my custom config file, is not even possible... – deczaloth Dec 14 '19 at 22:13
  • 1
    You may be able to rebuild the configurable binding redirect process. By register a method on the `AppDomain.CurrentDomain.AssemblyResolve` event and made this method get the binding rules from the config file. – Orace Dec 14 '19 at 22:19
  • 1
    I re-read your rationale. It looks like that your goal (separated exe and config) will have you to hard code where the program will find it's configuration. Sorry but I find it dumb. If the [relative] path of the config file change, you will have to recompile your code. I understand the benefits of a split, and for me the best solution is an app.config file with the runtime configuration and the [relative] path to an other config file, this other file will contain the rest of the configuration where the customer can tweak the application. – Orace Dec 14 '19 at 22:25
  • I get your point. Nonetheless, the folder structure where the config file is to be located has been unchanged for almost a more than a decade, so that would not be an issue. Anyway, i think at the end we would indeed apply such approach: "app.config file with the runtime configuration and the [relative] path to an other config file". – deczaloth Dec 14 '19 at 22:47
  • @Deczaloth take a look here https://stackoverflow.com/a/28500477/361177 – Orace Dec 14 '19 at 22:47
  • Hey! that one looks interesting. I will try it out as soon as i get back home. Thanks! – deczaloth Dec 14 '19 at 22:54
  • I tried the solution given in the link in your "Edit 2". Sadly it did not work in my case. I still get the same FileLoadException. I even tried resenting extra fields, but did not get it to work :( – deczaloth Dec 16 '19 at 08:52