¿Cómo puedo obtener mi aplicación de archivo único .NET Core 3 para encontrar el archivo appsettings.json?

11

¿Cómo se debe configurar una aplicación de API web .Net Core 3.0 de un solo archivo para buscar el appsettings.jsonarchivo que está en el mismo directorio en el que está construida la aplicación de un solo archivo?

despues de correr

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

El directorio se ve así:

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

Sin embargo, al intentar ejecutar los APPNAME.exeresultados en el siguiente error

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

Probé soluciones de una pregunta similar pero distinta , así como otras preguntas de Stack Overflow.

Intenté pasar lo siguiente a SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Cada uno condujo al mismo error.

La raíz del problema es que el PublishSingleFilebinario se descomprime y se ejecuta desde un tempdirectorio.

En el caso de esta aplicación de archivo único, la ubicación que buscaba appsettings.jsonera el siguiente directorio:

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

Todos los métodos anteriores apuntan al lugar donde se descomprime el archivo, que es diferente del lugar desde el que se ejecutó.

Jason Yandell
fuente

Respuestas:

14

Encontré un problema en GitHub aquí titulado PublishSingleFile excluding appsettings not working as expected.

Eso apuntó a otro problema aquí tituladosingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

En él, una solución era intentar Process.GetCurrentProcess().MainModule.FileName

El siguiente código configuró la aplicación para mirar el directorio desde el que se ejecutó la aplicación ejecutable única, en lugar del lugar en el que se extrajeron los archivos binarios.

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

La GetBasePath()implementación:

private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}
Jason Yandell
fuente
Esta respuesta, más @ ronald-swaine a continuación es perfecta. Los ajustes de aplicaciones se excluyen del exe incluido y la tarea de publicación coloca los archivos de ajustes de aplicación junto al exe incluido.
Aaron Hudon
8

Si está de acuerdo con que los archivos se usen en tiempo de ejecución fuera del ejecutable, entonces puede marcar los archivos que desea fuera, en csproj. Este método permite cambios en vivo y tal en una ubicación conocida.

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

Si esto no es aceptable, y debe tener SOLO un solo archivo, paso la ruta extraída de un solo archivo como la ruta raíz en la configuración de mi host. Esto permite la configuración y la maquinilla de afeitar (que agrego después) para encontrar sus archivos de manera normal.

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

Tenga en cuenta que para crear realmente un solo archivo y sin PDB, también necesitará:

<DebugType>None</DebugType>
Ronald Swaine
fuente
¿Cómo se visualiza Views \ Test.cshtml en su ejemplo en la computadora de destino? Tengo archivos de imagen y diccionario que necesito abrir basados ​​en Cultura,
Paul Cohen
@PaulCohen Normalmente implementaría todos los archivos desde la ubicación de salida publicada usando SCP. Con solo los cambios en csproj (primer ejemplo), cualquier API que utilice el directorio de contenido raíz predeterminado deberá tener sus archivos disponibles en el directorio de trabajo. Parece que necesita usar el segundo ejemplo, para obtener la ruta real del contenido extraído, de modo que pueda acceder a las imágenes desde una ruta completa, o al tener la raíz de contenido configurada correctamente para que ~ / ... rutas a las imágenes se pueden utilizar en maquinilla de afeitar.
Ronald Swaine
Mi experiencia está en aplicaciones integradas, por lo que un solo binario generalmente se graba en rom. De lo que me estoy dando cuenta es que existe el Exe que comparto en algún tipo de archivo "zip like" autoextraíble. Todos mis archivos de datos están ahí y cuando la aplicación se ejecuta, todo se extrae directamente a una temperatura. Si encuentro que puedo encontrar mis archivos de datos. También significa que necesito algo de lógica para poder depurar en VS donde los datos están en otro lugar.
Paul Cohen el
1
Actualmente tengo esta configuración, y al azar dejó de encontrar los archivos.
fue
1

Mi aplicación está en .NET Core 3.1, se publica como un solo archivo y se ejecuta como un Servicio de Windows (que puede o no tener un impacto en el problema).

La solución propuesta con Process.GetCurrentProcess().MainModule.FileNamela raíz de contenido funciona para mí, pero solo si configuro la raíz de contenido en el lugar correcto:

Esto funciona:

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

Esto no funciona:

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