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?
Application configuration in ASP.NET Core is performed using one or more configuration providers. Configuration providers read configuration data from key-value pairs using a variety of configuration sources: Settings files, such as appsettings. json.
Short version
.NET Core doesn't use app.config
, you'll have to upgrade to the new config system or manually manage the files.
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
to the App1.config
file to keep it out of the bundle. 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.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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With