Publishing a standalone exe file with .Net Core 3.0 and using an app.config
Asked Answered
T

3

11

I have a .Net Core 3.0 project and wanted to take advantage of the new option to publish the project as a single .exe file.

I use the command
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true

This, sure enough, produces a single .exe file but the problem is I have an app.config file in my project that the users should modify and I am not sure if it gets packaged in the .exe, as well. When I tested the produced .exe it reads the values I have initially put in the app.config.

I have tried setting the "Copy to Output Directory" option to "Copy always" but that didn't change anything. The "Build action" is set to "None".

How can I set it so that everything EXCEPT the app.config is packaged inside that .exe file, so that the user can change values?

Tuneless answered 17/10, 2019 at 11:1 Comment(12)
There's no app.config any more. Just delete it. Put any settings in appsettings.json or any other filer or configuration storage you may want. Even appsettings.json isn't a hard requirement, it's only a default nameLuthanen
@PanagiotisKanavos, what do you mean "There's no app.config any more"? Because stated like that it kinds of dismisses reality. :) What are the differences and benefits in using appsettings.json over app.config?Tuneless
There's no app.config any more. That's the reality. The values there aren't used or even read in any way. If you get any kind of values, they probably come from the generated Settings.cs defaults - which, by extension, means you converted an existing .NET Old project into .NET Core. The new configuration system is explained here. appsettings.json is only the default used by the default host builder. You can easily add other files, other sourcesLuthanen
The configuration system reads and merges values from all sources. Providers added later in the chain can override values read from earlier providers. This means you can publish your own json, xml or ini file and have the application read it. If you check the xml format supported by the file provider, it's nothing like the old app.config format.Luthanen
@PanagiotisKanavos, I'll take a look at your links but I created this project as a .Net Core Console Application, I haven't converted it from an old .Net project and I have added an app.config no problem. When I publish this project, I don't know if this should be so but, in the output I get an app.config and a <myExeName>.dll.config. If I change the values in the app.config, nothing happens. If I change the values in the .dll.config file - they are reflected, i.e. the program reads the new values.Tuneless
Unfortunately, there's a problem with single-file bundles and the base path defaults to the folder where the .exe bunlde is unpacked instead of the location of the .exe. Even with appsettings.json, you have to use Process.GetCurrentProcess().MainModule.FileName to find the host file's (original exe's) path. With the .NET Core config system, that's just annoying and you have to pass the full path to the setting files.Luthanen
How did you add the app.config and how do you read it though? Simply adding one to a new Winforms object creates an empty file App1.config and no Settings.cs file. The generated file is empty too. Are you using ConfigurationManager to read from it? In that case you may be able to use ConfigurationManager.OpenExeConfiguration with the host's pathLuthanen
@PanagiotisKanavos, I added it as Right click on project > Add > New File > Config (I don't remember exact name) and, yes, I am using ConfigurationManager, I thought that was the standard way?Tuneless
Not any more. I repeat, .config files aren't used in .NET Core. You can read it only because you explicitly load it through ConfigurationManager. Given the manual steps and the trouble, it's not worth doing this any more. With the new system you'd get multiple sources, DI, logging etc out of the box, while the config files would be a lot smallerLuthanen
That json file contains only what you want though. You can have different files per environment, you can combine them with other sources, including the command line, database and environment variables. At the end of the process, you can retrieve values without knowing where they came from. You can easily use strongly-typed config classes. Doing that with app.config was no trivial task. In the end, you have to do the same manual work.Luthanen
@PanagiotisKanavos, fair enough. I will switch. Thank you for everything! :)Tuneless
Besides, all .NET Core packages that want some type of configuration expect to use the .NET Core config system, and even provide helper functions for it. You'll have to add support for it at some point. And did I mention database sources? Think centralized, easily editable configuration.Luthanen
L
14

Short version

.NET Core doesn't use app.config, you'll have to upgrade to the new config system or manually manage the files.

  1. Add <ExcludeFromSingleFile>true</ExcludeFromSingleFile> to the App1.config file to keep it out of the bundle.
  2. Manually add a MyApp.exe.config file with the production settings and add <CopyToPublishDirectory>Always</CopyToPublishDirectory> to have it published to the Publish directory. Transformations won't run, so make sure it contains everything that's needed.
  3. Finally load the file explicitly to avoid a bug in the application's base path resolution
var hostFile=Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
ConfigurationManager.OpenExeConfiguration(hostFile+".config");

To load the published file, as if it were any other file


.NET Core 3, even for Windows Forms, doesn't use app.config. .NET Core's configuration system is described in Configuration in ASP.NET Core and despite the name, applies to every .NET Core application. It's far more powerful too, loading configuration from multiple sources, including files (even INI), databases, Azure or AWS setting stores etc.

Adding an Application Configuration File to a new Windows Forms project, both in VS 2019 and the command line creates an App1.config file with no special meaning as far as VS or .NET Core are concerned. Creating an AppName.exe.config requires actually creating a new AppName.exe.config file with the production settings.

The only way to read an Old-style .config file is to explicitly load it with ConfigurationManager.OpenExeConfiguration. This simply loads the file and parses it. One could pass any file path, or specify a ConfigurationUserLevel which simply resolves to a file location based on the executable's base directory.

And here's where the trouble starts. There's a bug.

With Single-file executables, all files are bundled in a single host file with the .exe extension. When that file runs for the first time, it unpacks its contents to AppData\Local\Temp\.net\, in a new folder named for the application. By design, the application's base directory should be the host's path, where the single .exe is. Unfortunately, there's a bug and the base directory remains the location of the bundle and the .dll that's actually run by the host.

That's why

System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None).FilePath 

returns C:\Users\myUser~1\AppData\Local\Temp\.net\ConsoleApp_NetCore\nk5sdmz5.ym1\ConsoleApp_NetCore.dll.config and I'd bet AppContext.BaseDirectory returns C:\Users\myUser~1\AppData\Local\Temp\.net\ConsoleApp_NetCore\nk5sdmz5.ym1\

The workaround for this is to retrieve the host's path and load the settings file explicitly. This means we can now use any file name. If we keep the old convention of naming the file appname.exe.config, we can just append .config to the host's full path:

var hostFile=Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
ConfigurationManager.OpenExeConfiguration(hostFile+".config");

This has to be done with the .NET Core File provider too.

Luthanen answered 17/10, 2019 at 12:44 Comment(8)
Did you mean Path.GetDirectoryName?Tuneless
By Jove I just now read your entire answer and you are a technical genius! Thank you very, very much for the detailed explanation and all your help! :)Tuneless
@J.Doe no, I run into this myself and ended up just stuffing everything into APPDATA because I had no time. Then someone else asked about the problem, I googled, blah, blah, I found that issue, facepalmed, started fixing my own code etc. Painful lessonLuthanen
I don't understand - in MSDN I see a Path.GetDirectoryName method nad Path.GetDirectory whines there's no such thing. But I also am not sure what you mean by ".NET Core File provider", so maybe I'm missing a NuGet package.Tuneless
@J.Doe that's just a typo. Googling and answering at the same time can have unintended consequencesLuthanen
Oh, OK. Because your previous comment started with "no" I thought you meant it wasn't supposed to be Path.GetDirectoryName like I asked in my first comment to your answer. :)Tuneless
Uhhh, just to point out - in your current code you are just passing the directory name + ".config" as the parameter for the full file path. I'd advise something like Process.GetCurrentProcess().MainModule.FileName.Replace(".exe", ".dll.config") or just Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + "\\app.config". If I could ask you to point me towards a resource that explains the difference between "app.config" and "myApp.dll.config" I'd be even more grateful! :)Tuneless
Separate point - although the code should, theoretically work fine and you are right it'd be better to migrate to .jsons, my program doesn't load values from either config, after I explicitly set it. :(Tuneless
H
2

You should mark the file as <ExcludeFromSingleFile>true</ExcludeFromSingleFile> in the project settings.

https://github.com/dotnet/core-setup/issues/7738 https://github.com/dotnet/designs/blob/master/accepted/single-file/design.md#build-system-interface

Highboy answered 17/10, 2019 at 11:14 Comment(2)
That's great, thanks! But now the program won't read from the .config file, at all. Neither from the "app.config" nor from the "<exeName>.dll.config" that popped up in the same folder as the .exe.Tuneless
UPDATE: using System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None).FilePath I get C:\Users\myUser~1\AppData\Local\Temp\.net\ConsoleApp_NetCore\nk5sdmz5.ym1\ConsoleApp_NetCore.dll.configTuneless
L
1

This works for me for a standalone exe. In my .csproj file I added this which created the myProgramName.dll.config upon build.

<ItemGroup>
 <None Update="App.config">
    <CopyToOutputDirectory>Never</CopyToOutputDirectory>
    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 </None>
</ItemGroup>

And in my C# console app code (dot net core 3.1.0) I added this.

// requires using System.Configuration;
string programName = "myProgramName"; 
var sourceHostFile = Directory.GetCurrentDirectory() + @"\" + programName + @".dll.config";
Console.WriteLine("sourceHostFile is: " + sourceHostFile);
// to load yourProgram.dll.config
// With Single-file executables, all files are bundled in a single host file with the .exe extension. 
// When that file runs for the first time, it unpacks its contents to AppData\Local\Temp\.net\, in a new folder named for the application
var targetHostFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
// ignore when in debug mode in vs ide
if (sourceHostFile != targetHostFile)
{
File.Copy(sourceHostFile, targetHostFile, true);
}
Console.WriteLine("targetHostFile is: " + targetHostFile);
string password = ConfigurationManager.AppSettings["password"];
Console.WriteLine("password from config is: " + password);
// end of poc
Lippold answered 11/6, 2020 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.