diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.docs/09- Dependency Injection & Service Registration.md b/src/Templates/Boilerplate/Bit.Boilerplate/.docs/09- Dependency Injection & Service Registration.md index 1e6b43b5c8..3ff858322c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.docs/09- Dependency Injection & Service Registration.md +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.docs/09- Dependency Injection & Service Registration.md @@ -250,6 +250,52 @@ public partial class FeedbackPage : AppPageBase } ``` +## Owned services + +### Default Service Lifetime in Blazor Components + +By default, services injected in Blazor components remain tied to the application scope for the entire lifetime: + +- **Blazor Server**: Until the user closes the browser tab or the browser gets disconnected. +- **Blazor WebAssembly / Blazor Hybrid**: Until the browser tab or app is closed. + +This is perfectly fine for most services (especially singletons or stateless ones), but services that hold resources (timers, event subscriptions, native handlers, etc.) may need to be disposed when their associated component is destroyed. + +### Using ScopedServices for Automatic Disposal + +To achieve automatic disposal when the component is disposed, inject the service via `ScopedServices` instead of using `[AutoInject]`. This creates a scoped service instance that gets disposed along with the component. + +**Example:** + +```csharp +Keyboard keyboard => field ??= ScopedServices.GetRequiredService(); // ??= means the service gets resolved when accessed, results into better performance. + +protected override async Task OnAfterFirstRenderAsync() +{ + await keyboard.Add(ButilKeyCodes.KeyF, () => searchBox.FocusAsync(), ButilModifiers.Ctrl); // Handles keyboard shortcuts + + await base.OnAfterFirstRenderAsync(); +} +``` + +Instead of + +```csharp +[AutoInject] private Keyboard keyboard = default!; + +protected override async Task OnAfterFirstRenderAsync() +{ + await keyboard.Add(ButilKeyCodes.KeyF, () => searchBox.FocusAsync(), ButilModifiers.Ctrl); // Handles keyboard shortcuts + + await base.OnAfterFirstRenderAsync(); +} + +protected override async ValueTask DisposeAsync(bool disposing) +{ + await keyboard.DisposeAsync(); + await base.DisposeAsync(disposing); +} +``` --- ### AI Wiki: Answered Questions diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppClientCoordinator.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppClientCoordinator.cs index a2226521ed..6ddb844e96 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppClientCoordinator.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppClientCoordinator.cs @@ -33,11 +33,9 @@ public partial class AppClientCoordinator : AppComponentBase [AutoInject] private UserAgent userAgent = default!; [AutoInject] private IJSRuntime jsRuntime = default!; [AutoInject] private IUserController userController = default!; - [AutoInject] private IStorageService storageService = default!; [AutoInject] private ILogger authLogger = default!; [AutoInject] private ILogger navigatorLogger = default!; [AutoInject] private ILogger logger = default!; - [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; //#if (notification == true) [AutoInject] private IPushNotificationService pushNotificationService = default!; //#endif @@ -362,7 +360,7 @@ private async Task ConfigureUISetup() if (CultureInfoManager.InvariantGlobalization is false) { CultureInfoManager.SetCurrentCulture(new Uri(NavigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture - await storageService.GetItem("Culture") ?? // 2- User settings + await StorageService.GetItem("Culture") ?? // 2- User settings CultureInfo.CurrentUICulture.Name); // 3- OS settings } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TodoPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TodoPage.razor.cs index 4943e9244c..65cff8d402 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TodoPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TodoPage.razor.cs @@ -5,9 +5,11 @@ namespace Boilerplate.Client.Core.Components.Pages; public partial class TodoPage { - [AutoInject] Keyboard keyboard = default!; [AutoInject] ITodoItemController todoItemController = default!; + // Refer to .docs/09- Dependency Injection & Service Registration.md 's Owned services section for more information about ScopedServices + Keyboard keyboard => field ??= ScopedServices.GetRequiredService(); + private bool isLoading; private string? searchText; private string? selectedSort; @@ -193,14 +195,4 @@ private async Task UpdateTodoItem(TodoItemDto todoItem) viewTodoItems.Remove(todoItem); } } - - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(true); - - if (disposing) - { - await keyboard.DisposeAsync(); - } - } }