this is the "piggy back" answer(s) area.
First, upvote the above answer by "RS" that I reference in this answer. That was the magic.
Short answer is "use RS's answer AND set that value in all the right places.". I show the 2 places to SET the values below.
My specific ADDITION (not mentioned anywhere else) is:
IConfigurationBuilder builder = new ConfigurationBuilder()
/* IMPORTANT line below */
.SetBasePath(realPath)
Longer answer is:
I needed the above answers AND I have some additions.
In my output (i'll show code later), here is the difference between the 2 answers above.
GetBasePath='/mybuilddir/myOut'
realPath='/var/tmp/.net/MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne/jhvc5zwc.g25'
where '/mybuilddir/myOut' was the location here I published my single file..in my docker definition file.
GetBasePath did NOT work when using PublishSingleFile
"realPath" was the way I finally got it to work. Aka, the answer above. : How can I get my .NET Core 3 single file app to find the appsettings.json file?
and when you see the value of "realPath"...then it all makes sense. the singleFile is being extracted ~somewhere....and RS figured out the magic sauce on where that extraction place is.
I will show my entire Program.cs, that will give context to everything.
Note, I had to set "realPath" in TWO places.
I marked the important things with
/* IMPORTANT
Full code below, which (again) borrows from RS's answer : How can I get my .NET Core 3 single file app to find the appsettings.json file?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
namespace MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne
{
public static class Program
{
public static async Task<int> Main(string[] args)
{
/* easy concrete logger that uses a file for demos */
Serilog.ILogger lgr = new Serilog.LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne.log.txt", rollingInterval: Serilog.RollingInterval.Day)
.CreateLogger();
try
{
/* look at the Project-Properties/Debug(Tab) for this environment variable */
string environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Console.WriteLine(string.Format("ASPNETCORE_ENVIRONMENT='{0}'", environmentName));
Console.WriteLine(string.Empty);
string basePath = Directory.GetCurrentDirectory();
basePath = GetBasePath();
Console.WriteLine(string.Format("GetBasePath='{0}'", basePath));
Console.WriteLine(string.Empty);
// 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
/* IMPORTANT 3 lines below */
string realPath = Directory.GetParent(System.Reflection.Assembly.GetExecutingAssembly().Location).FullName;
Console.WriteLine(string.Format("realPath='{0}'", realPath));
Console.WriteLine(string.Empty);
IConfigurationBuilder builder = new ConfigurationBuilder()
/* IMPORTANT line below */
.SetBasePath(realPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environmentName}.json", true, true)
.AddEnvironmentVariables();
IConfigurationRoot configuration = builder.Build();
IHost host = Host.CreateDefaultBuilder(args)
/* IMPORTANT line below */
.UseContentRoot(realPath)
.UseSystemd()
.ConfigureServices((hostContext, services) => AppendDi(services, configuration, lgr)).Build();
await host.StartAsync();
await host.WaitForShutdownAsync();
}
catch (Exception ex)
{
string flattenMsg = GenerateFullFlatMessage(ex, true);
Console.WriteLine(flattenMsg);
}
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
return 0;
}
private static string GetBasePath()
{
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
return Path.GetDirectoryName(processModule?.FileName);
}
private static string GenerateFullFlatMessage(Exception ex)
{
return GenerateFullFlatMessage(ex, false);
}
private static void AppendDi(IServiceCollection servColl, IConfiguration configuration, Serilog.ILogger lgr)
{
servColl
.AddSingleton(lgr)
.AddLogging();
servColl.AddHostedService<TimedHostedService>(); /* from https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio and/or https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/host/hosted-services/samples/3.x/BackgroundTasksSample/Services/TimedHostedService.cs */
servColl.AddLogging(blder =>
{
blder.AddConsole().SetMinimumLevel(LogLevel.Trace);
blder.SetMinimumLevel(LogLevel.Trace);
blder.AddSerilog(logger: lgr, dispose: true);
});
Console.WriteLine("Using UseInMemoryDatabase");
servColl.AddDbContext<WorkerServiceExampleOneDbContext>(options => options.UseInMemoryDatabase(databaseName: "WorkerServiceExampleOneInMemoryDatabase"));
}
private static string GenerateFullFlatMessage(Exception ex, bool showStackTrace)
{
string returnValue;
StringBuilder sb = new StringBuilder();
Exception nestedEx = ex;
while (nestedEx != null)
{
if (!string.IsNullOrEmpty(nestedEx.Message))
{
sb.Append(nestedEx.Message + System.Environment.NewLine);
}
if (showStackTrace && !string.IsNullOrEmpty(nestedEx.StackTrace))
{
sb.Append(nestedEx.StackTrace + System.Environment.NewLine);
}
if (ex is AggregateException)
{
AggregateException ae = ex as AggregateException;
foreach (Exception aeflatEx in ae.Flatten().InnerExceptions)
{
if (!string.IsNullOrEmpty(aeflatEx.Message))
{
sb.Append(aeflatEx.Message + System.Environment.NewLine);
}
if (showStackTrace && !string.IsNullOrEmpty(aeflatEx.StackTrace))
{
sb.Append(aeflatEx.StackTrace + System.Environment.NewLine);
}
}
}
nestedEx = nestedEx.InnerException;
}
returnValue = sb.ToString();
return returnValue;
}
}
}
and my toplayer csproj contents:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- allows one line of code to get a txt file logger #simple #notForProduction -->
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
and my docker file for kicks:
# See https://hub.docker.com/_/microsoft-dotnet-core-sdk/
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS buildImage
WORKDIR /mybuilddir
# Copy sln and csprojs and restore as distinct layers
COPY ./src/Solutions/MyCompany.MyExamples.WorkerServiceExampleOne.sln ./src/Solutions/
COPY ./src/ConsoleOne/*.csproj ./src/ConsoleOne/
RUN dotnet restore ./src/Solutions/MyCompany.MyExamples.WorkerServiceExampleOne.sln
COPY ./src ./src
RUN dotnet publish "./src/ConsoleOne/MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne.csproj" -c Release -o myOut -r linux-x64 /p:PublishSingleFile=true /p:DebugType=None --framework netcoreapp3.1
# See https://hub.docker.com/_/microsoft-dotnet-core-runtime/
FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS runtime
WORKDIR /myrundir
COPY --from=buildImage /mybuilddir/myOut ./
# this line is wrong for PublishSingleFile ### ENTRYPOINT ["dotnet", "MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne.dll"]
#below is probably right...i was still working on this at time of posting this answer
./myOut/MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne