Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get my .NET Core 3 single file app to find the appsettings.json file?

How should a single-file .Net Core 3.0 Web API application be configured to look for the appsettings.json file that is in the same directory that the single-file application is built to?

After running

dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true

The directory looks like this:

XX/XX/XXXX  XX:XX PM    <DIR>          .
XX/XX/XXXX  XX:XX PM    <DIR>          ..
XX/XX/XXXX  XX:XX PM               134 appsettings.json
XX/XX/XXXX  XX:XX PM        92,899,983 APPNAME.exe
XX/XX/XXXX  XX:XX PM               541 web.config
               3 File(s)     92,900,658 bytes

However, attempting to run APPNAME.exe results in the following error

An exception occurred, System.IO.FileNotFoundException: The configuration file 'appsettings.json' was not found and is not optional. The physical path is 'C:\Users\USERNAME\AppData\Local\Temp\.net\APPNAME\kyl3yc02.5zs\appsettings.json'.
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.HandleException(ExceptionDispatchInfo info)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load(Boolean reload)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
...

I tried solutions from a similar, but distinct question, as well as other Stack Overflow questions.

I attempted to pass the following to SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

  • Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)

Each led to the same error.

The root of the issue is that the PublishSingleFile binary is unzipped and run from a temp directory.

In the case of this single file app, the location it was looking appsettings.json was the following directory:

C:\Users\USERNAME\AppData\Local\Temp\.net\APPNAME\kyl3yc02.5zs

All of the above methods point to the place that the file is unzipped to, which is different than the place it was run from.

like image 376
Jason Yandell Avatar asked Oct 09 '19 15:10

Jason Yandell


People also ask

Where can I find Appsettings json?

appsettings. json is one of the several ways, in which we can provide the configuration values to ASP.NET core application. You will find this file in the root folder of our project. We can also create environment-specific files like appsettings.

How do I add Appsettings json in .NET Core console app?

Add Json File After adding the file, right click on appsettings. json and select properties. Then set “Copy to Ouptut Directory” option to Copy Always. Add few settings to json file, so that you can verify that those settings are loaded.

How Appsettings json works in .NET Core?

json file is generally used to store the application configuration settings such as database connection strings, any application scope global variables, and much other information. Actually, in ASP.NET Core, the application configuration settings can be stored in different configurations sources such as appsettings.


3 Answers

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); } 
like image 101
Jason Yandell Avatar answered Sep 23 '22 18:09

Jason Yandell


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>
like image 43
Ronald Swaine Avatar answered Sep 25 '22 18:09

Ronald Swaine


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

like image 33
Wolfgang Gallo Avatar answered Sep 22 '22 18:09

Wolfgang Gallo