应如何配置单文件.Net Core 3.0 Web API应用程序,以查找与构建单文件应用程序相同的目录中的appsettings.json
文件?
运行后
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true
目录如下:
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
但是,尝试运行APPNAME.exe
会导致以下错误
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()
...
我尝试了similar, but distinct question的解决方案以及其他Stack Overflow问题。
我试图将以下内容传递给SetBasePath()
Directory.GetCurrentDirectory()
environment.ContentRootPath
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
每个都导致了相同的错误。
问题的根源是PublishSingleFile
二进制文件已解压缩并从temp
目录运行。
对于此单个文件应用程序,其查找的位置appsettings.json
是以下目录:
C:\Users\USERNAME\AppData\Local\Temp\.net\APPNAME\kyl3yc02.5zs
以上所有方法均指向文件解压缩的位置,该位置与运行文件的位置不同。
答案 0 :(得分:6)
我在GitHub here上发现了一个标题为PublishSingleFile excluding appsettings not working as expected
的问题。
这指向另一个名为single file publish: AppContext.BaseDirectory doesn't point to apphost directory
的问题here
其中,一种解决方案是尝试Process.GetCurrentProcess().MainModule.FileName
以下代码将应用程序配置为查看运行单可执行应用程序的目录,而不是将二进制文件提取到的目录。
config.SetBasePath(GetBasePath());
config.AddJsonFile("appsettings.json", false);
GetBasePath()
实现:
private string GetBasePath()
{
using var processModule = Process.GetCurrentProcess().MainModule;
return Path.GetDirectoryName(processModule?.FileName);
}
答案 1 :(得分:5)
如果可以在运行时在可执行文件之外使用文件,则可以在csproj中标记所需的文件。这种方法允许在已知位置进行实时更改等。
hstore
如果这是不可接受的,并且必须仅包含一个文件,则在主机设置中将单文件提取的路径作为根路径传递。这样一来,配置和剃刀(我将在其后添加)就可以正常查找其文件。
<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>
注意,要真正制作一个文件而没有PDB,您还需要:
// 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);
答案 2 :(得分:2)
我的应用程序在.NET Core 3.1上,作为单个文件发布,并且作为Windows服务运行(这可能会或可能不会影响该问题)。
以Process.GetCurrentProcess().MainModule.FileName
作为内容根的建议解决方案对我有用,但前提是我将内容根设置在正确的位置:
这有效:
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseContentRoot(...);
webBuilder.UseStartup<Startup>();
});
这不起作用:
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.UseContentRoot(...)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
答案 3 :(得分:-1)
这是“背负式”答案区域。
首先,通过我在此答案中引用的“ RS”对上述答案进行投票。那是魔术。
最简单的答案是“使用RS的答案并在所有正确的位置设置该值”。我在下面显示了两个用于设置值的地方。
我的特定添加项(在其他任何地方都没有提及)是:
IConfigurationBuilder builder = new ConfigurationBuilder()
/* IMPORTANT line below */
.SetBasePath(realPath)
更长的答案是:
我需要以上答案,并且还有一些补充。
在我的输出中(稍后我将显示代码),这是上面两个答案之间的区别。
GetBasePath='/mybuilddir/myOut'
realPath='/var/tmp/.net/MyCompany.MyExamples.WorkerServiceExampleOne.ConsoleOne/jhvc5zwc.g25'
'/ mybuilddir / myOut'是我在docker定义文件中发布单个文件的位置。
使用PublishSingleFile时,GetBasePath不起作用
“ realPath”是我最终使它工作的方式。又名,以上答案。 :How can I get my .NET Core 3 single file app to find the appsettings.json file?
,当您看到“ realPath”的值时...那么一切都有意义。 singleFile正在某个位置被提取出来...。RS找出了提取位置所在的地方。
我将展示我的整个Program.cs,它将为所有内容提供上下文。
请注意,我必须在两个地方设置“ realPath”。
我用
标记了重要的事情
/* IMPORTANT
下面的完整代码(再次)是从RS的答案中借用的: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://docs.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;
}
}
}
和我的toplayer csproj内容:
<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>
和我的docker文件踢:
# 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