Como faço para que meu aplicativo de arquivo único do .NET Core 3 encontre o arquivo appsettings.json?

11

Como um aplicativo de API da Web .Net Core 3.0 de arquivo único deve ser configurado para procurar o appsettings.jsonarquivo que está no mesmo diretório em que o aplicativo de arquivo único é criado?

Depois de correr

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

O diretório fica assim:

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

No entanto, tentar executar APPNAME.exeresultados no seguinte erro

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

Tentei soluções a partir de uma pergunta semelhante, mas distinta , bem como de outras questões do Stack Overflow.

Tentei passar o seguinte para SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Cada um deles levou ao mesmo erro.

A raiz do problema é que o PublishSingleFilebinário é descompactado e executado a partir de um tempdiretório.

No caso deste aplicativo de arquivo único, o local que ele estava procurando appsettings.jsonera o seguinte diretório:

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

Todos os métodos acima apontam para o local em que o arquivo está descompactado, diferente do local em que foi executado.

Jason Yandell
fonte

Respostas:

14

Encontrei um problema no GitHub aqui intitulado PublishSingleFile excluding appsettings not working as expected.

Isso apontou para outra questão aqui intituladasingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

Nele, uma solução era tentar Process.GetCurrentProcess().MainModule.FileName

O código a seguir configurou o aplicativo para examinar o diretório em que o aplicativo executável único foi executado, e não o local em que os binários foram extraídos.

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

A GetBasePath()implementação:

private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}
Jason Yandell
fonte
Esta resposta, além de @ ronald-swaine abaixo, é perfeita. As configurações de aplicativos são excluídas do pacote configurável exe e a tarefa de publicação coloca os arquivos de configurações de aplicativos ao lado do pacote configurável exe.
Aaron Hudon em 03/02
8

Se você concorda em ter arquivos usados ​​em tempo de execução fora do executável, basta sinalizar os arquivos que deseja fora, no csproj. Este método permite alterações em tempo real e em um local conhecido.

<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 isso não é aceitável e deve ter APENAS um único arquivo, passo o caminho extraído por um único arquivo como o caminho raiz na configuração do meu host. Isso permite que a configuração e o razor (que adiciono depois) encontrem seus arquivos 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);

Observe que, para criar realmente um único arquivo e sem PDB, você também precisará:

<DebugType>None</DebugType>
Ronald Swaine
fonte
Como o Views \ Test.cshtml no seu exemplo chega ao computador de destino? Tenho arquivos de imagem e de dicionário que eu preciso abrir base para a Cultura,
Paul Cohen
@PaulCohen Normalmente, eu implantava todos os arquivos do local de saída publicado usando o SCP. Com apenas as alterações em csproj (primeiro exemplo), qualquer API que estiver usando o diretório de conteúdo raiz padrão precisará ter seus arquivos disponíveis no diretório de trabalho. Parece que você precisa usar o segundo exemplo, para obter o caminho real do conteúdo extraído, para que você possa acessar as imagens de um caminho completo ou para que a raiz do conteúdo esteja definida corretamente para que ~ / ... caminhos imagens podem ser usadas no barbeador.
Ronald Swaine
Meu background é em aplicativos incorporados, portanto, um único binário geralmente é gravado em rom. O que estou percebendo é que existe o Exe que eu compartilho em algum tipo de arquivo "zip like" auto-extraível. Todos os meus arquivos de dados estão lá e, quando o aplicativo é executado, tudo é extraído diretamente para uma temperatura. Se eu achar que posso encontrar meus arquivos de dados. Isso também significa que preciso de alguma lógica para poder depurar no VS, onde os dados estão em outro lugar.
Paul Cohen
11
Atualmente, tenho essa configuração e ela parou aleatoriamente de encontrar os arquivos.
Indo em
1

Meu aplicativo está no .NET Core 3.1, é publicado como um único arquivo e é executado como um Serviço do Windows (que pode ou não ter impacto no problema).

A solução proposta com Process.GetCurrentProcess().MainModule.FileNamea raiz do conteúdo funciona para mim, mas somente se eu definir a raiz do conteúdo no lugar certo:

Isso funciona:

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

Isso não funciona:

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