Nabs.Launchpad.Ui.Shell
10.0.210
Prefix Reserved
dotnet add package Nabs.Launchpad.Ui.Shell --version 10.0.210
NuGet\Install-Package Nabs.Launchpad.Ui.Shell -Version 10.0.210
<PackageReference Include="Nabs.Launchpad.Ui.Shell" Version="10.0.210" />
<PackageVersion Include="Nabs.Launchpad.Ui.Shell" Version="10.0.210" />
<PackageReference Include="Nabs.Launchpad.Ui.Shell" />
paket add Nabs.Launchpad.Ui.Shell --version 10.0.210
#r "nuget: Nabs.Launchpad.Ui.Shell, 10.0.210"
#:package Nabs.Launchpad.Ui.Shell@10.0.210
#addin nuget:?package=Nabs.Launchpad.Ui.Shell&version=10.0.210
#tool nuget:?package=Nabs.Launchpad.Ui.Shell&version=10.0.210
Nabs Launchpad UI Shell Library
The Nabs Launchpad UI Shell library provides core UI services and messaging infrastructure for Blazor applications. This library implements global loading state management using the MVVM messaging pattern, enabling coordinated UI feedback across components without tight coupling.
Key Features
- Global Loading State Management: Centralized control of loading indicators across the application
- MVVM Messaging: Built on CommunityToolkit.Mvvm's WeakReferenceMessenger for memory-safe communication
- Simple API: Static methods for easy integration from any component or service
- Decoupled Architecture: Components subscribe to loading state changes without direct references
- Thread-Safe: Safe for use across multiple threads and components
- Memory Efficient: Uses weak references to prevent memory leaks
Core Components
LoadingService
Static service providing global loading state control with three simple methods: Start(), Stop(), and Spin(bool).
LoadingStateMessage
Message class carrying loading state changes through the MVVM messenger infrastructure.
Usage Examples
Basic Loading State Control
// In a service or code-behind
public async Task LoadDataAsync()
{
// Start loading indicator
LoadingService.Start();
try
{
await FetchDataFromApiAsync();
// Process data...
}
finally
{
// Always stop loading indicator
LoadingService.Stop();
}
}
Using Spin for Toggle
public async Task ProcessItemsAsync()
{
LoadingService.Spin(true); // Start loading
try
{
foreach (var item in items)
{
await ProcessAsync(item);
}
}
finally
{
LoadingService.Spin(false); // Stop loading
}
}
Subscribing to Loading State Changes
@page "/dashboard"
@implements IDisposable
@inject IMessenger Messenger
@if (isLoading)
{
<div class="loading-overlay">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
}
<div class="content">
</div>
@code {
private bool isLoading = false;
protected override void OnInitialized()
{
// Subscribe to loading state changes
Messenger.Register<LoadingStateMessage>(this, (recipient, message) =>
{
isLoading = message.Value;
InvokeAsync(StateHasChanged);
});
}
public void Dispose()
{
// Clean up subscription
Messenger.UnregisterAll(this);
}
}
Global Progress Component
@* GlobalProgress.razor *@
@implements IRecipient<LoadingStateMessage>
@implements IDisposable
@inject IMessenger Messenger
@if (IsLoading)
{
<div class="global-progress-overlay">
<div class="progress-spinner">
<span class="spinner-border spinner-border-lg"></span>
<p>Loading...</p>
</div>
</div>
}
@code {
private bool IsLoading { get; set; }
protected override void OnInitialized()
{
Messenger.RegisterAll(this);
}
public void Receive(LoadingStateMessage message)
{
IsLoading = message.Value;
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
Messenger.UnregisterAll(this);
}
}
Integration with ViewModels
public class UserManagementViewModel : BaseItemViewModel<UserDto>
{
private readonly IUserService _userService;
public UserManagementViewModel(IUserService userService, IStringLocalizer localizer)
: base(localizer)
{
_userService = userService;
}
public async Task LoadUsersAsync()
{
LoadingService.Start();
try
{
var result = await _userService.GetAllUsersAsync();
if (result.IsSuccess)
{
// Process users
}
}
finally
{
LoadingService.Stop();
}
}
}
Nested Loading Scenarios
public class DataSyncService
{
public async Task SyncAllDataAsync()
{
LoadingService.Start();
try
{
// Sync users - inner loading states are ignored
await SyncUsersAsync();
// Sync orders
await SyncOrdersAsync();
// Sync products
await SyncProductsAsync();
}
finally
{
LoadingService.Stop();
}
}
private async Task SyncUsersAsync()
{
// This won't interfere with outer loading state
LoadingService.Start();
try
{
await Task.Delay(1000);
}
finally
{
LoadingService.Stop();
}
}
}
Loading State in HTTP Interceptor
public class LoadingHttpMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
LoadingService.Start();
try
{
return await base.SendAsync(request, cancellationToken);
}
finally
{
LoadingService.Stop();
}
}
}
// Register in Program.cs
builder.Services.AddTransient<LoadingHttpMessageHandler>();
builder.Services.AddHttpClient("api")
.AddHttpMessageHandler<LoadingHttpMessageHandler>();
API Reference
LoadingService
Static service for controlling global loading state.
Methods
Start
public static void Start()
Starts the global loading indicator by sending a loading state message with value true.
Usage:
LoadingService.Start();
Stop
public static void Stop()
Stops the global loading indicator by sending a loading state message with value false.
Usage:
LoadingService.Stop();
Spin
public static void Spin(bool value)
Sets the loading state to the specified value.
Parameters:
value:trueto start loading,falseto stop
Usage:
LoadingService.Spin(isLoading);
LoadingStateMessage
Message class for communicating loading state changes.
Constructor
public LoadingStateMessage(bool isLoading)
Creates a new loading state message.
Parameters:
isLoading: The loading state value
Properties
Value
public bool Value { get; }
Gets the loading state value (inherited from ValueChangedMessage<bool>).
Architecture and Design Patterns
MVVM Messaging Pattern
The library uses CommunityToolkit.Mvvm's WeakReferenceMessenger to implement a publish-subscribe pattern:
- Publisher:
LoadingServicepublishesLoadingStateMessageinstances - Subscribers: Components register to receive messages
- Weak References: Prevents memory leaks from forgotten subscriptions
Singleton Service Pattern
LoadingService is implemented as a static class providing a singleton-like behavior without requiring dependency injection registration.
Message Broadcasting
When a loading state changes, all registered subscribers receive the message simultaneously, enabling coordinated UI updates.
Best Practices
1. Always Use Try-Finally
// Good
LoadingService.Start();
try
{
await DoWorkAsync();
}
finally
{
LoadingService.Stop();
}
// Bad - loading might not stop on exception
LoadingService.Start();
await DoWorkAsync();
LoadingService.Stop();
2. Dispose Subscriptions
// Implement IDisposable in components
public void Dispose()
{
Messenger.UnregisterAll(this);
}
3. Use InvokeAsync for State Changes
Messenger.Register<LoadingStateMessage>(this, (r, m) =>
{
isLoading = m.Value;
InvokeAsync(StateHasChanged); // Required for Blazor components
});
4. Centralize Loading Indicators
Create a single global progress component rather than multiple loading indicators throughout the application.
5. Avoid Nesting Issues
Be careful with nested operations that both control loading state. Consider using a counter-based approach for complex scenarios.
6. Consider User Experience
- Don't show loading for operations < 300ms
- Provide cancellation options for long operations
- Show progress details when possible
Integration with Launchpad Components
With Core.ViewModels
public class MyViewModel : BaseItemViewModel<MyDto>
{
protected override async Task LoadDataAsync()
{
LoadingService.Start();
try
{
// Load data
}
finally
{
LoadingService.Stop();
}
}
}
With Ui.Shell.Blazor.Sf
The Syncfusion UI Shell library includes a pre-built GlobalProgress component that subscribes to loading state messages.
With Core.Portal
Portal applications can use the loading service in middleware or during authentication.
Memory Management
Weak References
The messenger uses weak references, which means:
- Subscribers don't prevent garbage collection
- No memory leaks from forgotten unregistrations
- Components are still cleaned up properly
Manual Cleanup (Recommended)
public void Dispose()
{
// Explicitly unregister for cleaner resource management
Messenger.UnregisterAll(this);
}
Thread Safety
The loading service is thread-safe:
- Can be called from any thread
- Multiple simultaneous calls are safe
- Message delivery is thread-safe
// Safe to call from multiple threads
await Task.WhenAll(
Task.Run(() => Operation1()),
Task.Run(() => Operation2()),
Task.Run(() => Operation3())
);
async Task Operation1()
{
LoadingService.Start();
try { /* work */ }
finally { LoadingService.Stop(); }
}
Testing
Unit Testing with Loading Service
[Fact]
public async Task Service_ShowsLoadingState()
{
// Arrange
var messenger = WeakReferenceMessenger.Default;
var loadingStates = new List<bool>();
messenger.Register<LoadingStateMessage>(this, (r, m) =>
{
loadingStates.Add(m.Value);
});
var service = new MyService();
// Act
await service.ProcessAsync();
// Assert
loadingStates.Should().Contain(true); // Started loading
loadingStates.Should().Contain(false); // Stopped loading
// Cleanup
messenger.UnregisterAll(this);
}
Testing Components with Loading State
[Fact]
public void Component_RespondsToLoadingState()
{
// Arrange
using var ctx = new TestContext();
var component = ctx.RenderComponent<MyComponent>();
// Act - Send loading message
WeakReferenceMessenger.Default.Send(new LoadingStateMessage(true));
// Assert
component.Find(".loading-indicator").Should().NotBeNull();
// Act - Stop loading
WeakReferenceMessenger.Default.Send(new LoadingStateMessage(false));
// Assert
component.FindAll(".loading-indicator").Should().BeEmpty();
}
Advanced Scenarios
Loading with Progress Percentage
// Define custom message
public class LoadingProgressMessage : ValueChangedMessage<(bool IsLoading, int Percentage)>
{
public LoadingProgressMessage(bool isLoading, int percentage)
: base((isLoading, percentage)) { }
}
// Service with progress
public static class ProgressLoadingService
{
public static void ReportProgress(int percentage)
{
WeakReferenceMessenger.Default.Send(
new LoadingProgressMessage(true, percentage));
}
}
Multiple Loading Contexts
// Different loading contexts
public enum LoadingContext
{
Global,
Grid,
Form
}
public class ContextualLoadingMessage : ValueChangedMessage<(LoadingContext Context, bool IsLoading)>
{
public ContextualLoadingMessage(LoadingContext context, bool isLoading)
: base((context, isLoading)) { }
}
Troubleshooting
Loading Indicator Doesn't Appear
Issue: Loading state changes but UI doesn't update
Solutions:
- Ensure you're using
InvokeAsync(StateHasChanged)in the message handler - Verify the component is registered as a message recipient
- Check that the global progress component is included in your layout
Loading Never Stops
Issue: Loading indicator stays visible
Solutions:
- Ensure
Stop()is called in afinallyblock - Check for exceptions that prevent finally execution
- Verify no other code is calling
Start()without matchingStop()
Memory Leaks
Issue: Components not being garbage collected
Solutions:
- Implement
IDisposableand callMessenger.UnregisterAll(this) - Don't capture
thisin lambda expressions that outlive the component - Use weak reference patterns for long-lived subscriptions
Dependencies
- CommunityToolkit.Mvvm: For MVVM messaging infrastructure and weak reference messenger
- Nabs.Launchpad.Core.Context: For portal and user context integration
Related Libraries
- Nabs.Launchpad.Ui.Shell.Blazor.Sf: Syncfusion-based UI components with built-in loading indicators
- Nabs.Launchpad.Core.ViewModels: ViewModel base classes that can integrate with loading service
- Nabs.Launchpad.Core.Portal: Portal infrastructure that may use loading states
Target Framework
- .NET 10
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CommunityToolkit.Mvvm (>= 8.4.0)
- Nabs.Launchpad.Core.Context (>= 10.0.210)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Nabs.Launchpad.Ui.Shell:
| Package | Downloads |
|---|---|
|
Nabs.Launchpad.Core.ViewModels
Package Description |
|
|
Nabs.Launchpad.Core.Portal
Package Description |
|
|
Nabs.Launchpad.Ui.Shell.Blazor.Sf
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 10.0.210 | 0 | 12/30/2025 | |
| 10.0.209 | 47 | 12/30/2025 | |
| 10.0.208 | 55 | 12/30/2025 | |
| 10.0.207 | 70 | 12/29/2025 | |
| 10.0.206 | 72 | 12/29/2025 | |
| 10.0.205 | 165 | 12/24/2025 | |
| 10.0.204 | 188 | 12/21/2025 | |
| 10.0.203 | 282 | 12/18/2025 | |
| 10.0.202 | 278 | 12/17/2025 | |
| 10.0.200 | 287 | 12/17/2025 | |
| 10.0.199 | 442 | 12/10/2025 | |
| 10.0.197 | 183 | 12/5/2025 | |
| 10.0.196 | 682 | 12/3/2025 | |
| 10.0.195 | 687 | 12/3/2025 | |
| 10.0.194 | 686 | 12/3/2025 | |
| 10.0.193 | 691 | 12/2/2025 | |
| 10.0.192 | 190 | 11/28/2025 | |
| 10.0.190 | 202 | 11/27/2025 | |
| 10.0.189 | 181 | 11/23/2025 | |
| 10.0.187 | 179 | 11/23/2025 | |
| 10.0.186 | 163 | 11/23/2025 | |
| 10.0.184 | 417 | 11/20/2025 | |
| 10.0.181-rc3 | 295 | 11/11/2025 | |
| 10.0.180 | 299 | 11/11/2025 | |
| 10.0.179-rc2 | 294 | 11/11/2025 | |
| 10.0.178-rc2 | 240 | 11/10/2025 | |
| 10.0.177-rc2 | 239 | 11/10/2025 | |
| 10.0.176-rc2 | 190 | 11/6/2025 | |
| 10.0.175-rc2 | 198 | 11/6/2025 | |
| 10.0.174-rc2 | 193 | 11/5/2025 | |
| 10.0.173-rc2 | 302 | 11/3/2025 | |
| 10.0.172-rc2 | 226 | 11/2/2025 | |
| 10.0.170-rc2 | 210 | 11/1/2025 | |
| 10.0.169-rc2 | 214 | 11/1/2025 | |
| 10.0.168-rc2 | 206 | 10/31/2025 | |
| 10.0.166-rc2 | 215 | 10/31/2025 | |
| 10.0.164-rc2 | 275 | 10/28/2025 | |
| 10.0.162-rc2 | 271 | 10/24/2025 | |
| 10.0.161 | 276 | 10/24/2025 | |
| 9.0.151 | 234 | 10/17/2025 | |
| 9.0.150 | 308 | 9/10/2025 | |
| 9.0.146 | 251 | 8/15/2025 | |
| 9.0.145 | 295 | 8/11/2025 | |
| 9.0.144 | 309 | 8/8/2025 | |
| 9.0.137 | 273 | 7/29/2025 | |
| 9.0.136 | 267 | 7/29/2025 | |
| 9.0.135 | 276 | 7/28/2025 | |
| 9.0.134 | 308 | 7/9/2025 | |
| 9.0.133 | 317 | 7/9/2025 | |
| 9.0.132 | 302 | 7/9/2025 | |
| 9.0.131 | 331 | 7/9/2025 | |
| 9.0.130 | 306 | 7/7/2025 |