How can I get my .NET Core 3 single file app to find the appsettings.json file?
If you're okay with having files used at runtime outside of the executable, then you could just flag the files you want outside, in csproj. This method allows for live changes and such in a known location.
<ItemGroup>
<None Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
<None Include="appsettings.Development.json;appsettings.QA.json;appsettings.Production.json;">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<DependentUpon>appsettings.json</DependentUpon>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Views\Test.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
</ItemGroup>
If this is not acceptable, and must have ONLY a single file, I pass the single-file-extracted path as the root path in my host setup. This allows configuration, and razor (which I add after), to find its files as normal.
// when using single file exe, the hosts config loader defaults to GetCurrentDirectory
// which is where the exe is, not where the bundle (with appsettings) has been extracted.
// when running in debug (from output folder) there is effectively no difference
var realPath = Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName;
var host = Host.CreateDefaultBuilder(args).UseContentRoot(realPath);
Note, to truly make a single file, and no PDB, you'll also need:
<DebugType>None</DebugType>
I found an issue on GitHub here titled PublishSingleFile excluding appsettings not working as expected
.
That pointed to another issue here titled single file publish: AppContext.BaseDirectory doesn't point to apphost directory
In it, a solution was to try Process.GetCurrentProcess().MainModule.FileName
The following code configured the application to look at the directory that the single-executable application was run from, rather than the place that the binaries were extracted to.
config.SetBasePath(GetBasePath());
config.AddJsonFile("appsettings.json", false);
The GetBasePath()
implementation:
private string GetBasePath()
{
using var processModule = Process.GetCurrentProcess().MainModule;
return Path.GetDirectoryName(processModule?.FileName);
}
My application is on .NET Core 3.1, is published as a single file and runs as a Windows Service (which may or may not have an impact on the issue).
The proposed solution with Process.GetCurrentProcess().MainModule.FileName
as the content root works for me, but only if I set the content root in the right place:
This works:
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseContentRoot(...);
webBuilder.UseStartup<Startup>();
});
This does not work:
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.UseContentRoot(...)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});