Come posso ottenere la mia app per file singoli .NET Core 3 per trovare il file appsettings.json?


11

Come dovrebbe essere configurata un'applicazione API Web .Net Core 3.0 a file singolo per cercare il appsettings.jsonfile che si trova nella stessa directory in cui è costruita l'applicazione a file singolo?

Dopo aver corso

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

La directory è simile alla seguente:

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

Tuttavia, il tentativo di eseguire APPNAME.exeprovoca il seguente errore

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

Ho provato soluzioni da una domanda simile, ma distinta , così come altre domande Stack Overflow.

Ho provato a passare il seguente a SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Ciascuno ha portato allo stesso errore.

La radice del problema è che il file PublishSingleFilebinario è decompresso ed eseguito da una tempdirectory.

Nel caso di questa app a file singolo, la posizione in cui si trovava appsettings.jsonera la seguente directory:

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

Tutti i metodi sopra indicati puntano al punto in cui il file è decompresso, che è diverso dal luogo da cui è stato eseguito.

Risposte:


14

Ho trovato un problema su GitHub qui intitolato PublishSingleFile excluding appsettings not working as expected.

Ciò ha indicato un altro problema qui intitolatosingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

In esso, una soluzione era provare Process.GetCurrentProcess().MainModule.FileName

Il codice seguente ha configurato l'applicazione per esaminare la directory da cui è stata eseguita l'applicazione a singolo eseguibile, anziché il luogo in cui sono stati estratti i file binari.

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

L' GetBasePath()implementazione:

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

Questa risposta, più @ ronald-swaine di seguito è perfetta. Le impostazioni delle app sono escluse dall'exe in bundle e l'attività di pubblicazione posiziona i file delle appettings accanto all'exe in bundle.
Aaron Hudon,

8

Se stai bene con i file utilizzati in fase di esecuzione al di fuori dell'eseguibile, puoi semplicemente contrassegnare i file che desideri all'esterno, in csproj. Questo metodo consente modifiche in tempo reale e simili in una posizione nota.

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

Se questo non è accettabile e deve avere SOLO un singolo file, passo il percorso estratto da un singolo file come percorso principale nella configurazione del mio host. Ciò consente alla configurazione e al rasoio (che aggiungo dopo) di trovare i suoi file normalmente.

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

Nota, per creare veramente un singolo file e nessun PDB, avrai anche bisogno di:

<DebugType>None</DebugType>

In che modo Views \ Test.cshtml nel tuo esempio arriva sul computer di destinazione? Ho file di immagini e dizionari che devo aprire in base a Cultura,
Paul Cohen,

@PaulCohen In genere distribuivo tutti i file dalla posizione di output pubblicata usando SCP. Con solo le modifiche in csproj (primo esempio) qualsiasi API che sta usando la directory del contenuto principale predefinita dovrà avere i propri file disponibili nella directory di lavoro. Sembra che tu debba usare il secondo esempio, per ottenere il vero percorso del contenuto estratto, in questo modo puoi accedere alle immagini da un percorso completo o avere il root del contenuto impostato correttamente in modo che ~ / ... percorsi alle immagini possono essere utilizzate nel rasoio.
Ronald Swaine,

Il mio background è nelle applicazioni integrate, quindi un singolo binario viene solitamente masterizzato in rom. Quello che sto realizzando è l'Exe che condivido in una sorta di file "zip like" autoestraente. Tutti i miei file di dati sono lì dentro e quando l'applicazione viene eseguita tutto viene estratto direttamente a una temperatura. Se trovo che posso trovare i miei file di dati. Significa anche che ho bisogno di un po 'di logica in modo da poter eseguire il debug in VS dove i dati sono altrove.
Paul Cohen,

1
Attualmente ho questa configurazione e ha smesso casualmente di trovare i file.
Andato via il

1

La mia applicazione è su .NET Core 3.1, è pubblicata come un singolo file e funziona come un servizio Windows (che può avere o meno un impatto sul problema).

La soluzione proposta con Process.GetCurrentProcess().MainModule.FileNamecome root del contenuto funziona per me, ma solo se ho impostato la root del contenuto nel posto giusto:

Questo funziona:

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

Questo non funziona:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .UseContentRoot(...)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.