Wie kann ich meine .NET Core 3-Einzeldateianwendung dazu bringen, die Datei appsettings.json zu finden?


11

Wie sollte eine .NET Core 3.0-Web-API-Anwendung mit einer einzelnen Datei so konfiguriert werden, dass nach der appsettings.jsonDatei gesucht wird, die sich in demselben Verzeichnis befindet, in dem die Einzeldateianwendung erstellt wurde?

Nach dem Rennen

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

Das Verzeichnis sieht folgendermaßen aus:

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

Der Versuch, etwas auszuführen, APPNAME.exeführt jedoch zu folgendem Fehler

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()
...

Ich habe Lösungen aus einer ähnlichen, aber unterschiedlichen Frage sowie aus anderen Fragen zum Stapelüberlauf ausprobiert .

Ich habe versucht, folgendes zu übergeben SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Jeder führte zu demselben Fehler.

Die Wurzel des Problems ist, dass die PublishSingleFileBinärdatei entpackt und aus einem tempVerzeichnis ausgeführt wird.

Bei dieser App mit einer einzelnen Datei war der gesuchte Speicherort appsettings.jsondas folgende Verzeichnis:

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

Alle oben genannten Methoden verweisen auf den Ort, an den die Datei entpackt wird, der sich von dem Ort unterscheidet, an dem sie ausgeführt wurde.

Antworten:


14

Ich fand ein Thema auf GitHub hier betitelt PublishSingleFile excluding appsettings not working as expected.

Das spitzt zu einer neuen Ausgabe hier betiteltensingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

Darin bestand eine Lösung darin, es zu versuchen Process.GetCurrentProcess().MainModule.FileName

Mit dem folgenden Code wurde die Anwendung so konfiguriert, dass das Verzeichnis angezeigt wird, in dem die einzelne ausführbare Anwendung ausgeführt wurde, und nicht der Ort, an den die Binärdateien extrahiert wurden.

config.SetBasePath(GetBasePath());
config.AddJsonFile("appsettings.json", false);

Die GetBasePath()Implementierung:

private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}

Diese Antwort plus @ ronald-swaine unten ist perfekt. Die Appsettings werden von der gebündelten Exe ausgeschlossen, und die Veröffentlichungsaufgabe platziert die Appsettings-Dateien neben der gebündelten Exe.
Aaron Hudon

8

Wenn Sie damit einverstanden sind, dass Dateien zur Laufzeit außerhalb der ausführbaren Datei verwendet werden, können Sie die gewünschten Dateien einfach in csproj markieren. Diese Methode ermöglicht Live-Änderungen und dergleichen an einem bekannten Ort.

<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>

Wenn dies nicht akzeptabel ist und NUR eine einzelne Datei enthalten darf, übergebe ich den aus einer einzelnen Datei extrahierten Pfad als Stammpfad in meinem Host-Setup. Dadurch können die Konfiguration und der Rasierer (den ich später hinzufüge) die Dateien wie gewohnt finden.

// 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);

Beachten Sie, dass Sie außerdem Folgendes benötigen, um wirklich eine einzelne Datei und keinen PDB zu erstellen:

<DebugType>None</DebugType>

Wie wird Views \ Test.cshtml in Ihrem Beispiel auf den Zielcomputer übertragen? Ich habe Bild- und Wörterbuchdateien, die ich basierend auf Culture öffnen muss,
Paul Cohen

@PaulCohen Normalerweise würde ich alle Dateien vom veröffentlichten Ausgabespeicherort mithilfe von SCP bereitstellen. Mit nur den Änderungen in csproj (erstes Beispiel) müssen alle APIs, die das Standard-Stamminhaltsverzeichnis verwenden, ihre Dateien im Arbeitsverzeichnis verfügbar haben. Es hört sich so an, als müssten Sie das zweite Beispiel verwenden, um den tatsächlichen Pfad des extrahierten Inhalts abzurufen, damit Sie über einen vollständigen Pfad auf die Bilder zugreifen können, oder indem Sie den Inhaltsstamm korrekt so einstellen, dass ~ / ... Pfade zu Bildern kann in Rasiermesser verwendet werden.
Ronald Swaine

Mein Hintergrund liegt in eingebetteten Anwendungen, daher wird normalerweise eine einzelne Binärdatei in rom gebrannt. Was mir klar wird, ist die Exe, die ich in einer Art selbstextrahierender "zip like" -Datei teile. Alle meine Datendateien befinden sich dort und wenn die Anwendung ausgeführt wird, wird alles direkt in eine temporäre Datei extrahiert. Wenn ich finde, dass ich meine Datendateien finden kann. Es bedeutet auch, dass ich eine Logik benötige, damit ich in VS debuggen kann, wo sich die Daten woanders befinden.
Paul Cohen

1
Ich habe dieses Setup derzeit und es hat zufällig aufgehört, die Dateien zu finden.
Gehen-weg

1

Meine Anwendung befindet sich unter .NET Core 3.1, wird als einzelne Datei veröffentlicht und als Windows-Dienst ausgeführt (was sich möglicherweise auf das Problem auswirkt oder nicht).

Die vorgeschlagene Lösung mit Process.GetCurrentProcess().MainModule.FileName als Inhaltsstamm funktioniert für mich, aber nur, wenn ich den Inhaltsstamm an der richtigen Stelle einstelle:

Das funktioniert:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseContentRoot(...);
        webBuilder.UseStartup<Startup>();
    });

Das funktioniert nicht:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .UseContentRoot(...)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.