Как получить однофайловое приложение .NET Core 3 для поиска файла appsettings.json?

11

Как настроить однофайловое приложение .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()
...

Я пытался найти решения по схожему, но отдельному вопросу , а также по другим вопросам переполнения стека.

Я попытался передать следующее SetBasePath()

  • Directory.GetCurrentDirectory()

  • environment.ContentRootPath

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

Каждый приводил к одной и той же ошибке.

Корень проблемы в том, что PublishSingleFileдвоичный файл разархивирован и запускается из tempкаталога.

В случае этого единственного файлового приложения местоположение, которое это искало, appsettings.jsonбыло следующим каталогом:

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

Все вышеперечисленные методы указывают на место разархивирования файла, которое отличается от места, из которого он был запущен.

Джейсон Янделл
источник

Ответы:

14

Я нашел проблему на GitHub здесь под названием PublishSingleFile excluding appsettings not working as expected.

Это указал на другой вопрос здесь под названиемsingle file publish: AppContext.BaseDirectory doesn't point to apphost directory

В этом решении было попробовать 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);
}
Джейсон Янделл
источник
Этот ответ, плюс @ ronald-swaine ниже, идеален. Наборы приложений исключаются из прилагаемого exe-файла, а задача публикации помещает файлы appsettings вместе с входящим в комплект exe-файлом.
Аарон Худон
8

Если у вас все в порядке с использованием файлов во время выполнения вне исполняемого файла, тогда вы можете просто пометить файлы, которые вы хотите, за пределами, в csproj. Этот метод допускает живые изменения и тому подобное в известном месте.

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

Если это неприемлемо и должно иметь ТОЛЬКО один файл, я передаю путь, извлеченный из одного файла, как корневой путь в моей настройке хоста. Это позволяет конфигурации и бритве (которую я добавляю после) найти файлы как обычно.

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

Обратите внимание, что для создания единого файла без использования PDB вам также потребуется:

<DebugType>None</DebugType>
Рональд Суэйн
источник
Как Views \ Test.cshtml в вашем примере попадает на целевой компьютер? У меня есть файлы изображений и словарей, которые мне нужно открыть на основе культуры,
Пол Коэн
@PaulCohen Я бы обычно развертывал все файлы из опубликованного выходного каталога, используя SCP. Только с изменениями в csproj (первый пример) любые API, использующие корневой каталог содержимого по умолчанию, должны будут иметь свои файлы доступными в рабочем каталоге. Похоже, вам нужно использовать 2-й пример, чтобы получить реальный путь к извлеченному контенту, чтобы вы могли получить доступ к изображениям по полному пути или правильно установив корень контента, чтобы пути ~ / ... чтобы изображения можно было использовать в бритве.
Рональд Свейн
Мой опыт работы со встроенными приложениями, поэтому один двоичный файл обычно записывается в ROM. Я понимаю, что есть Exe, который я разделяю в каком-то самораспаковывающемся «zip-подобном» файле. Все мои файлы данных находятся там, и когда приложение запускается, все извлекается непосредственно в temp. Если я нахожу это, я могу найти свои файлы данных. Это также означает, что мне нужна логика, чтобы я мог отлаживать в VS, где данные находятся в другом месте.
Пол Коэн
1
У меня есть эта настройка в настоящее время, и она случайно перестала находить файлы.
ушел
1

Мое приложение работает на .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>();
    });
Вольфганг Галло
источник