diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..2adf293 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,70 @@ +# Правила для GitHub Copilot + +- Всегда отвечай, используя русский язык +- Всегда пиши комментарии в коде на русском языке + +## Комментарии +- Короткие пояснительные комментарии располагай в конце той же строки, что и код // кратко по делу +- Старайся избегать тривиальных комментариев + +## XML‑документация +- Документируй классы, структуры, делегаты, перечисления и их члены только XML‑комментариями +- Одинарное предложение пиши в одной строке внутри тега и без точки в конце +- Каждый тег XML‑комментария располагай на отдельной строке +- Порядок тегов: `` → `` → `` → `` → `` → `` +- Для сложных публичных метдов генерируй блок с простым примером использования кода внутри тега `` + +Примеры: +- `Краткое описание сущности` +- `Описание параметра` +- `Описание возвращаемого значения` + +## Синтаксис и минимализм +- При генерации кода используй современные конструкции языка, совместимые с целевыми платформами проекта +- Стремись минимизировать количество фигурных скобок за счёт expression‑bodied членов и switch‑выражений +- Не убирай фигурные скобки в многострочных конструкциях ради читаемости +- Всегда старайся минимизировать размер кода, если не запрошено иное + +Разрешённые современные приёмы (когда поддерживается целевой платформой): +- file‑scoped namespace +- expression‑bodied члены +- switch‑выражения и pattern matching +- target‑typed `new` +- collection expressions и инициализаторы коллекций +- `using var` и `await using` +- операторы `??`, `??=`, `is not`, `with` +- упрощение nullable-присвоения `target?.Property = 15;` вместо `if(target is not null) target.Property = 15;` + +## Именование +- Локальные переменные: `snake_case` +- Параметры методов: `PascalCase` +- Поля экземпляров: `_PascalCase` +- Статические поля: `__PascalCase` +- Константы: `PascalCase` +- Публичные типы и члены API: `PascalCase` +- Предпочитай английский язык при именовании переменных, методов, классов и прочих сущностей + +## Инициализация и объявления +- При инициализации массивов, списков и словарей используй выражения инициализации массивов/коллекций +- При объявлении переменных предпочитай использовать ключевое слово `var` (кроме случаев, когда явный тип заметно повышает понятность) + +## Форматирование +- Короткие системные комментарии пиши компактно в одну строку +- Удаляй неиспользуемые `using`, сортируй и группируй директивы `using` +- Разделяй логические блоки пустыми строками по мере необходимости, избегай лишних переносов + +## Практики .NET +- Включай `#nullable enable` там, где это поддерживается +- Используй guard‑выражения, например `ArgumentNullException.ThrowIfNull(x)` +- Предпочитай Try‑паттерны для контроля потока вместо исключений +- При генерации метода добавляй в его начале блок проверки входных параметров. Отделяй этот блок пустой строкой от остального тела метода +- При генерации публичных свойств у моделей-представления MVVM (классов, реализующих INotifyPropertyChanged) используй следующий формат (в одну строку): +```csharp +/// Описание свойства +public string PropertyName { get; set => Set(ref field, value); } +``` +- Для простых лаконичных методов используй expression‑bodied синтаксис, записанный в одну строку. + +## Совместимость целей +- В рабочем пространстве используются целевые платформы: `.NET Standard 2.0` и `.NET 10` +- Применяй современные возможности языка и платформы только если они доступны для соответствующей целевой платформы проекта \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8bb4cbb..becc6ed 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Cache NuGet uses: actions/cache@v3 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 5020f5e..ccf2cd6 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -28,9 +28,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - - name: Cache NuGet + - name: Cache NuGet uses: actions/cache@v3 with: path: ~/.nuget/packages diff --git a/MathCore.Hosting.WPF.sln b/MathCore.Hosting.WPF.sln index 9560836..a5826c1 100644 --- a/MathCore.Hosting.WPF.sln +++ b/MathCore.Hosting.WPF.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32421.90 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11205.157 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MathCore.Hosting.WPF", "MathCore.Hosting.WPF\MathCore.Hosting.WPF.csproj", "{3D43B887-FC43-4074-B027-30E3F54EAC9F}" EndProject @@ -9,6 +9,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A61C5345 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MathCore.Hosting.WPF.TestWPF", "Tests\MathCore.Hosting.WPF.TestWPF\MathCore.Hosting.WPF.TestWPF.csproj", "{0F4EEB55-74E5-4075-BBE0-FA8DBBE397E5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Service", ".Service", "{28372A79-1495-45D6-A555-D9D5F75FE7F8}" + ProjectSection(SolutionItems) = preProject + .github\copilot-instructions.md = .github\copilot-instructions.md + .github\workflows\publish.yml = .github\workflows\publish.yml + .github\workflows\testing.yml = .github\workflows\testing.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/MathCore.Hosting.WPF.sln.DotSettings b/MathCore.Hosting.WPF.sln.DotSettings new file mode 100644 index 0000000..8b2dd86 --- /dev/null +++ b/MathCore.Hosting.WPF.sln.DotSettings @@ -0,0 +1,9 @@ + + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/MathCore.Hosting.WPF/ApplicationHosting.cs b/MathCore.Hosting.WPF/ApplicationHosting.cs index 40de892..72dc206 100644 --- a/MathCore.Hosting.WPF/ApplicationHosting.cs +++ b/MathCore.Hosting.WPF/ApplicationHosting.cs @@ -1,9 +1,67 @@ -using MathCore.DI; +using System.Diagnostics.CodeAnalysis; +using MathCore.DI; // ReSharper disable EventNeverSubscribedTo.Global namespace MathCore.Hosting.WPF; /// Приложение WPF с поддержкой механизмов хоста и контейнера сервисов +/// +/// Для использования необходимо унаследовать ваш класс приложения от данного абстрактного класса и корректно указать его в корневой разметке App.xaml +/// Регистрацию пользовательских сервисов можно выполнить через статическое событие ConfigureServices или методы ServicesAdd/ServicesRemove +/// +/// +/// Пример настройки приложения: +/// 1. Создаём класс App.xaml.cs, наследуя его от ApplicationHosting: +/// +/// using MathCore.Hosting.WPF; +/// using Microsoft.Extensions.DependencyInjection; +/// using Microsoft.Extensions.Hosting; +/// +/// namespace MyApp; +/// +/// public interface IMyService { void Do(); } +/// public class MyService : IMyService { public void Do() { /* реализация */ } } +/// +/// public partial class App : ApplicationHosting +/// { +/// static App() +/// { +/// // Подписка на событие конфигурации сервисов один раз при загрузке типа +/// ConfigureServices += OnConfigureServices; +/// } +/// +/// private static void OnConfigureServices(HostBuilderContext context, IServiceCollection services) +/// { +/// // Регистрация собственных сервисов приложения +/// services.AddSingleton(); +/// } +/// } +/// +/// 2. Изменяем корень файла App.xaml, указывая локальный класс (унаследован от ApplicationHosting): +/// +/// +/// +/// +/// +/// 3. Использование зарегистрированного сервиса в окне: +/// +/// using Microsoft.Extensions.DependencyInjection; +/// +/// public partial class MainWindow : Window +/// { +/// public MainWindow() +/// { +/// InitializeComponent(); +/// var my_service = ApplicationHosting.Services.GetRequiredService(); +/// my_service.Do(); +/// } +/// } +/// +/// public abstract class ApplicationHosting : Application { /// Событие возникает в момент первичной конфигурации хоста @@ -31,7 +89,8 @@ public abstract class ApplicationHosting : Application LoadingServiceFromExecutingAssembly, ]; - public static IReadOnlyList ErrorLoadingServicesAssemblies { get; private set; } = Array.Empty(); + /// Список сборок, в которых произошли ошибки при загрузке сервисов + public static IReadOnlyList ErrorLoadingServicesAssemblies { get; private set; } = []; private static void LoadingServiceFromExecutingAssembly(HostBuilderContext Host, IServiceCollection services) { @@ -85,10 +144,9 @@ private static void LoadingServiceFromExecutingAssembly(HostBuilderContext Host, /// Текущее окно public static Window? CurrentWindow => FocusedWindow ?? ActiveWindow ?? Current.MainWindow; - private static IHost? __Hosting; - /// Хост приложения - public static IHost Hosting => __Hosting ??= CreateHostBuilder(Environment.GetCommandLineArgs()) + [field: MaybeNull, AllowNull] + public static IHost Hosting => field ??= CreateHostBuilder(Environment.GetCommandLineArgs()) .AddServiceLocator() .Build(); @@ -124,22 +182,62 @@ public static IHostBuilder CreateHostBuilder(string[] Args) return builder; } + /// Переопределяет логику старта приложения для инициализации хоста и контейнера сервисов protected override async void OnStartup(StartupEventArgs e) { - var host = Hosting; - Resources["ServiceLocator"] = new ServiceLocatorHosted(); - base.OnStartup(e); - // ReSharper disable once AsyncApostle.AsyncAwaitMayBeElidedHighlighting - await host.StartAsync().ConfigureAwait(false); - - __HostBuilderConfigurations.Clear(); - __ServicesConfigurators.Clear(); + try + { + var host = Hosting; + Resources["ServiceLocator"] = new ServiceLocatorHosted(); + base.OnStartup(e); + // ReSharper disable once AsyncApostle.AsyncAwaitMayBeElidedHighlighting + await host.StartAsync().ConfigureAwait(false); + + __HostBuilderConfigurations.Clear(); + __ServicesConfigurators.Clear(); + } + catch (Exception error) + { + if(!HandleStartupException(error)) + // ReSharper disable once AsyncVoidThrowException + throw; + } } + /// + /// Переопределяет логику обработки исключений, возникающих при старте приложения + /// + /// Возникшее в процессе выполнения метода исключение + /// + /// Истина, если исключение обработано и его повторная генерация не требуется - приложение продолжит работать; + /// Ложь, если исключение не обработано и его необходимо повторно сгенерировать - приложение завершит работу. + /// + protected virtual bool HandleStartupException(Exception error) => false; + + /// Переопределяет логику завершения приложения для корректной остановки хоста protected override async void OnExit(ExitEventArgs e) { - using var host = Hosting; - base.OnExit(e); - await host.StopAsync().ConfigureAwait(false); + try + { + using var host = Hosting; + base.OnExit(e); + await host.StopAsync().ConfigureAwait(false); + } + catch (Exception error) + { + if(!HandleExitException(error)) + // ReSharper disable once AsyncVoidThrowException + throw; + } } + + /// + /// Переопределяет логику обработки исключений, возникающих при завершении приложения + /// + /// Возникшее в процессе выполнения метода исключение + /// + /// Истина, если исключение обработано и его повторная генерация не требуется - приложение продолжит завершение работы; + /// Ложь, если исключение не обработано и его необходимо повторно сгенерировать - приложение завершит работу с ошибкой. + /// + protected virtual bool HandleExitException(Exception error) => false; } \ No newline at end of file diff --git a/MathCore.Hosting.WPF/Extensions/CommandEx.cs b/MathCore.Hosting.WPF/Extensions/CommandEx.cs index 901707d..45b82e1 100644 --- a/MathCore.Hosting.WPF/Extensions/CommandEx.cs +++ b/MathCore.Hosting.WPF/Extensions/CommandEx.cs @@ -1,10 +1,16 @@ -using MathCore.WPF.Commands; +#nullable enable +using MathCore.WPF.Commands; using Microsoft.Extensions.Logging; namespace MathCore.Hosting.WPF.Extensions; +/// Методы расширения для конфигурации команд public static class CommandEx { + /// Добавляет логирование событий выполнения команды + /// Команда к которой добавляется логирование + /// Логгер используемый для записи событий команды + /// Исходная команда с подключёнными обработчиками логирования public static TCommand WithLogging(this TCommand command, ILogger logger) where TCommand : Command { command.BeforeExecuted += BeforeCommandExecuting; @@ -13,19 +19,10 @@ public static TCommand WithLogging(this TCommand command, ILogger logg return command; - void BeforeCommandExecuting(object? Sender, EventArgs E) - { - logger.LogInformation("Command {0} start executing with parameter {1}", command, E.Argument); - } + void BeforeCommandExecuting(object? Sender, EventArgs E) => logger.LogInformation("Command {command} start executing with parameter {parameter}", command, E.Argument); // логируем старт выполнения - void OnCommandExecuted(object? Sender, EventArgs E) - { - logger.LogInformation("Command {0} executed successful with parameter {1}", command, E.Argument); - } + void OnCommandExecuted(object? Sender, EventArgs E) => logger.LogInformation("Command {command} executed successful with parameter {parameter}", command, E.Argument); // логируем успешное выполнение - void OnCommandExecutingError(object Sender, ExceptionEventHandlerArgs Args) - { - logger.LogError("Command {0} thrown error {1}", command, Args.Argument); - } + void OnCommandExecutingError(object Sender, ExceptionEventHandlerArgs Args) => logger.LogError("Command {command} thrown error {exception}", command, Args.Argument); // логируем ошибку выполнения } } diff --git a/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj b/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj index b640422..da848bb 100644 --- a/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj +++ b/MathCore.Hosting.WPF/MathCore.Hosting.WPF.csproj @@ -3,6 +3,7 @@ Library + net10.0-windows; net9.0-windows; net8.0-windows; net7.0-windows; @@ -19,7 +20,7 @@ - 0.0.14.2 + 1.0.0 Добавлен метод-расширение для MathCore.WPF.Command, обеспечивающий возможностьб логирования процесса выполнения @@ -37,6 +38,7 @@ MathCore.Hosting.WPF.snk true snupkg + NuGetReadme.md @@ -50,8 +52,8 @@ - - + + @@ -72,4 +74,8 @@ + + + + diff --git a/MathCore.Hosting.WPF/WindowViewModelAttribute.cs b/MathCore.Hosting.WPF/WindowViewModelAttribute.cs index 26dea16..7a58d20 100644 --- a/MathCore.Hosting.WPF/WindowViewModelAttribute.cs +++ b/MathCore.Hosting.WPF/WindowViewModelAttribute.cs @@ -6,6 +6,7 @@ namespace MathCore.Hosting.WPF; /// Модель-представления окна +[AttributeUsage(AttributeTargets.Class)] public class WindowViewModelAttribute(Type WindowType) : Attribute { public WindowViewModelAttribute() : this(null!) { } @@ -18,6 +19,7 @@ public WindowViewModelAttribute() : this(null!) { } #if NET7_0_OR_GREATER /// Модель-представления окна +[AttributeUsage(AttributeTargets.Class)] public sealed class WindowViewModelAttribute() : Attribute where TWindow : Window { /// Тип окна для модели-представления diff --git a/MathCore.Hosting.WPF/readme.md b/MathCore.Hosting.WPF/readme.md new file mode 100644 index 0000000..b3430e7 --- /dev/null +++ b/MathCore.Hosting.WPF/readme.md @@ -0,0 +1,130 @@ +# MathCore.Hosting.WPF + +Инфраструктурный NuGet‑пакет для интеграции `Generic Host` (`Microsoft.Extensions.Hosting`) и DI (`Microsoft.Extensions.DependencyInjection`) в WPF‑приложения без ручного шаблонного кода. + +## Задачи которые решает +- Единый `IHost` внутри WPF `Application` +- Простая регистрация сервисов и фоновых служб +- Автоматическое сканирование сборок и подхват сервисов +- Удобный доступ к `IHost`, `IServiceProvider`, `IConfiguration` +- Встраиваемый `ServiceLocator` как ресурс XAML + +## Установка +```bash +dotnet add package MathCore.Hosting.WPF +``` + +## Быстрый старт +1. Наследуйтесь от `ApplicationHosting`: +```csharp +using MathCore.Hosting.WPF; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace MyApp; + +public interface IMyService { void Do(); } +public class MyService : IMyService { public void Do() { /* логика */ } } + +public partial class App : ApplicationHosting +{ + static App() => ConfigureServices += OnConfigureServices; // подписка один раз при загрузке типа + + private static void OnConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.AddSingleton(); // регистрация сервисов + } +} +``` +2. Корневой тег `App.xaml`: +```xml + +``` +3. Использование сервиса в окне: +```csharp +using Microsoft.Extensions.DependencyInjection; +using MathCore.Hosting.WPF; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + var svc = ApplicationHosting.Services.GetRequiredService(); + svc.Do(); + } +} +``` + +## Регистрация через методы +```csharp +// Добавление (например в статическом конструкторе App) +ServicesAdd((ctx, services) => services.AddSingleton()); + +// Удаление +ServicesRemove(myConfigurator); + +// Очистка +ServicesClear(); +``` + +## Настройка HostBuilder +```csharp +HostBuilderConfiguratorAdd(builder => +{ + builder.ConfigureLogging(logging => logging.AddDebug()); +}); +``` + +## Фоновая служба пример +```csharp +public class Worker : BackgroundService +{ + private readonly ILogger _Logger; + public Worker(ILogger logger) => _Logger = logger; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while(!stoppingToken.IsCancellationRequested) + { + _Logger.LogInformation("Tick {Time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } +} + +ConfigureServices += (_, services) => services.AddHostedService(); +``` + +## Доступ к инфраструктуре +```csharp +var host = ApplicationHosting.Hosting; // IHost +var services = ApplicationHosting.Services; // IServiceProvider +var configuration = ApplicationHosting.Configuration; // IConfiguration +``` + +## Авто‑регистрация сервисов +Пакет пытается вызвать `services.AddServicesFromAssembly(assembly)` для сборок: +- содержащих тип с методом `Main` +- имеющих атрибуты с именем содержащим `Service` +(Системные/Interop/Blend/Microsoft сборки пропускаются.) Ошибочные сборки сохраняются в `ErrorLoadingServicesAssemblies`. + +## Жизненный цикл +- `OnStartup` строит и запускает хост +- `OnExit` корректно останавливает хост +- После запуска списки конфигураторов очищаются чтобы избежать повторной регистрации + +## Советы +- Подписывайтесь на события один раз (статический конструктор) +- Долгие операции выносите в `BackgroundService` +- Используйте DI для ViewModel и служб вместо прямого ServiceLocator + +## Цели +.NET 6–10, .NET Framework 4.6.1–4.8 + +## Лицензия +MIT diff --git a/Tests/MathCore.Hosting.WPF.TestWPF/MathCore.Hosting.WPF.TestWPF.csproj b/Tests/MathCore.Hosting.WPF.TestWPF/MathCore.Hosting.WPF.TestWPF.csproj index e893ef5..2fe713d 100644 --- a/Tests/MathCore.Hosting.WPF.TestWPF/MathCore.Hosting.WPF.TestWPF.csproj +++ b/Tests/MathCore.Hosting.WPF.TestWPF/MathCore.Hosting.WPF.TestWPF.csproj @@ -2,7 +2,7 @@ WinExe - net9.0-windows + net10.0-windows enable enable preview diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d9b643c --- /dev/null +++ b/readme.md @@ -0,0 +1,168 @@ +# MathCore.Hosting.WPF + +`MathCore.Hosting.WPF` – вспомогательный пакет для интеграции механизма `Generic Host` и контейнера DI (`Microsoft.Extensions.DependencyInjection`) в WPF‑приложение. +Пакет упрощает: +- построение и запуск `IHost` внутри WPF `Application` +- регистрацию и получение сервисов через статический локатор +- автозагрузку сервисов из сборок +- доступ к `IConfiguration` и окружению хоста + +## Возможности +- Базовый класс `ApplicationHosting` для наследования вместо стандартного `Application` +- Статические события/методы для настройки хоста и регистрации сервисов +- Автоматическое сканирование сборок и добавление сервисов (по эвристике) +- Ресурс `ServiceLocator` доступный из XAML и кода +- Поддержка многих целевых платформ (.NET 6–10 + .NET Framework 4.6.1–4.8) + +## Установка +```bash +# Через .NET CLI +dotnet add package MathCore.Hosting.WPF + +# Через Package Manager +Install-Package MathCore.Hosting.WPF +``` + +## Быстрый старт +### 1. Наследуемся от `ApplicationHosting` +`App.xaml.cs`: +```csharp +using MathCore.Hosting.WPF; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace MyApp; + +public interface IMyService { void Do(); } +public class MyService : IMyService { public void Do() { /* логика */ } } + +public partial class App : ApplicationHosting +{ + static App() + { + // Подписка единожды – при загрузке типа + ConfigureServices += OnConfigureServices; + } + + private static void OnConfigureServices(HostBuilderContext context, IServiceCollection services) + { + // Регистрация пользовательских сервисов + services.AddSingleton(); + } +} +``` + +### 2. Меняем корневой тег `App.xaml` +```xml + + + +``` + +### 3. Используем сервисы в окне +```csharp +using Microsoft.Extensions.DependencyInjection; +using MathCore.Hosting.WPF; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + var my_service = ApplicationHosting.Services.GetRequiredService(); + my_service.Do(); + } +} +``` + +## Альтернативные способы регистрации +Помимо события `ConfigureServices` можно: +```csharp +// Добавить конфигуратор программно (например, в статическом конструкторе App) +ServicesAdd((context, services) => +{ + services.AddSingleton(); +}); + +// Удалить ранее добавленный конфигуратор +ServicesRemove(myConfigurator); + +// Очистить все пользовательские конфигураторы +ServicesClear(); +``` + +Для настройки самого `HostBuilder`: +```csharp +HostBuilderConfiguratorAdd(builder => +{ + builder.ConfigureLogging(logging => + { + logging.AddDebug(); + }); +}); +``` + +## Получение сервисов в XAML через ресурс локатора +После запуска приложение добавляет ресурс: +```xaml +{StaticResource ServiceLocator} +``` +Пример привязки (через `ObjectDataProvider` или конвертер) – обычно предпочтительнее получать сервисы в коде/VM. + +## Автозагрузка сервисов из сборок +Встроенный конфигуратор автоматически вызывает `services.AddServicesFromAssembly(assembly)` для сборок: +- содержащих тип с методом `Main` +- имеющих атрибуты, имя которых включает слово `Service` + +Исключаются системные/Interop/Blend и сборки компании Microsoft. Сборки, не прошедшие загрузку типов (`ReflectionTypeLoadException`), сохраняются в `ApplicationHosting.ErrorLoadingServicesAssemblies`. + +## Доступ к окружению и конфигурации +```csharp +var configuration = ApplicationHosting.Configuration; // IConfiguration +var host = ApplicationHosting.Hosting; // IHost +var provider = ApplicationHosting.Services; // IServiceProvider +``` + +## Жизненный цикл +- `OnStartup` автоматически строит и запускает `IHost` +- `OnExit` останавливает и освобождает хост +- После запуска внутренние списки конфигураторов очищаются (чтобы избежать повторной регистрации при переразборе XAML / design‑time) + +## Рекомендации +- Подписывайтесь на `ConfigureServices` только один раз в статическом конструкторе приложения +- Избегайте долгих операций в конфигурировании – переносите их в фоновые службы (`IHostedService`) +- Для тестов можно создавать временный `HostBuilder` через `CreateHostBuilder(args)` + +## Пример фоновой службы +```csharp +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +public class Worker : BackgroundService +{ + private readonly ILogger _logger; + public Worker(ILogger logger) => _logger = logger; // внедрение через DI + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while(!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("Tick {Time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } +} + +// Регистрация +ConfigureServices += (_, services) => services.AddHostedService(); +``` + +## Лицензия +Проект распространяется под лицензией автора (см. файл LICENSE при наличии) + +## Обратная связь +Issue / идеи / предложения: раздел Issues репозитория GitHub.