Excalibur.Dispatch.Transport.AzureServiceBus
3.0.0-alpha.19
This is a prerelease version of Excalibur.Dispatch.Transport.AzureServiceBus.
dotnet add package Excalibur.Dispatch.Transport.AzureServiceBus --version 3.0.0-alpha.19
NuGet\Install-Package Excalibur.Dispatch.Transport.AzureServiceBus -Version 3.0.0-alpha.19
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Excalibur.Dispatch.Transport.AzureServiceBus" Version="3.0.0-alpha.19" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Excalibur.Dispatch.Transport.AzureServiceBus" Version="3.0.0-alpha.19" />
<PackageReference Include="Excalibur.Dispatch.Transport.AzureServiceBus" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Excalibur.Dispatch.Transport.AzureServiceBus --version 3.0.0-alpha.19
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Excalibur.Dispatch.Transport.AzureServiceBus, 3.0.0-alpha.19"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Excalibur.Dispatch.Transport.AzureServiceBus@3.0.0-alpha.19
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Excalibur.Dispatch.Transport.AzureServiceBus&version=3.0.0-alpha.19&prerelease
#tool nuget:?package=Excalibur.Dispatch.Transport.AzureServiceBus&version=3.0.0-alpha.19&prerelease
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Excalibur.Dispatch.Transport.AzureServiceBus
Azure messaging transport implementation for the Excalibur framework, providing integration with Azure Service Bus, Event Hubs, and Storage Queues.
Overview
This package provides Azure messaging integration for Excalibur.Dispatch, enabling:
- Azure Service Bus: Enterprise messaging with queues, topics, and sessions
- Azure Event Hubs: High-throughput event streaming with partitions
- Azure Storage Queues: Simple, cost-effective queue storage
- CloudEvents Support: Standards-compliant structured and binary event formatting
- Managed Identity: Passwordless authentication with Azure AD
- Dead Letter Handling: Built-in dead letter queue support
Installation
dotnet add package Excalibur.Dispatch.Transport.AzureServiceBus
Configuration
Service Bus
Using Connection String
services.Configure<AzureServiceBusOptions>(options =>
{
options.ConnectionString = "Endpoint=sb://mynamespace.servicebus.windows.net/;SharedAccessKeyName=...";
options.QueueName = "my-queue";
});
Using Managed Identity (Recommended)
services.Configure<AzureServiceBusOptions>(options =>
{
options.Namespace = "mynamespace.servicebus.windows.net";
options.QueueName = "my-queue";
});
services.Configure<AzureProviderOptions>(options =>
{
options.UseManagedIdentity = true;
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
});
Environment Variables
AZURE_SERVICEBUS_CONNECTIONSTRING=Endpoint=sb://...
AZURE_SERVICEBUS_QUEUENAME=my-queue
services.Configure<AzureServiceBusOptions>(configuration.GetSection("Azure:ServiceBus"));
Event Hubs
Connection String
services.Configure<AzureEventHubOptions>(options =>
{
options.ConnectionString = "Endpoint=sb://mynamespace.servicebus.windows.net/;...";
options.EventHubName = "my-eventhub";
options.ConsumerGroup = "$Default";
});
Managed Identity
services.Configure<AzureEventHubOptions>(options =>
{
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.EventHubName = "my-eventhub";
options.ConsumerGroup = "my-consumer-group";
});
Storage Queues
Connection String
services.Configure<AzureStorageQueueOptions>(options =>
{
options.ConnectionString = "DefaultEndpointsProtocol=https;AccountName=...";
options.QueueName = "my-queue";
});
Managed Identity
services.Configure<AzureStorageQueueOptions>(options =>
{
options.StorageAccountUri = new Uri("https://mystorageaccount.queue.core.windows.net/");
options.QueueName = "my-queue";
});
Authentication
Managed Identity (Production Recommended)
services.Configure<AzureProviderOptions>(options =>
{
options.UseManagedIdentity = true;
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
});
Required Azure RBAC roles:
- Service Bus:
Azure Service Bus Data Sender,Azure Service Bus Data Receiver - Event Hubs:
Azure Event Hubs Data Sender,Azure Event Hubs Data Receiver - Storage Queues:
Storage Queue Data Contributor
Service Principal
services.Configure<AzureProviderOptions>(options =>
{
options.TenantId = "your-tenant-id";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
});
Key Vault Integration
services.Configure<AzureProviderOptions>(options =>
{
options.KeyVaultUrl = new Uri("https://mykeyvault.vault.azure.net/");
options.UseManagedIdentity = true;
});
Message Configuration
Service Bus Settings
services.Configure<AzureServiceBusOptions>(options =>
{
// Connection
options.Namespace = "mynamespace.servicebus.windows.net";
options.QueueName = "my-queue";
options.TransportType = ServiceBusTransportType.AmqpTcp; // or AmqpWebSockets
// Performance
options.MaxConcurrentCalls = 10; // Concurrent message processing
options.PrefetchCount = 50; // Messages to prefetch
// CloudEvents
options.CloudEventsMode = CloudEventsMode.Structured; // or Binary
// Error handling
options.DeadLetterOnRejection = true; // Send rejected messages to DLQ
// Security
options.EnableEncryption = false;
});
Event Hubs Settings
services.Configure<AzureEventHubOptions>(options =>
{
// Connection
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.EventHubName = "my-eventhub";
options.ConsumerGroup = "$Default";
// Performance
options.PrefetchCount = 300; // Events to prefetch
options.MaxBatchSize = 100; // Max events per batch
// Processing
options.StartingPosition = EventHubStartingPosition.Latest; // or Earliest
// Security
options.EnableEncryption = false;
options.EncryptionProviderName = null;
// Debugging
options.EnableVerboseLogging = false;
});
Storage Queue Settings
services.Configure<AzureStorageQueueOptions>(options =>
{
// Connection
options.StorageAccountUri = new Uri("https://mystorageaccount.queue.core.windows.net/");
options.QueueName = "my-queue";
// Processing
options.MaxConcurrentMessages = 10; // Concurrent processing
options.MaxMessages = 10; // Messages per poll (max 32)
options.PollingInterval = TimeSpan.FromSeconds(1);
options.VisibilityTimeout = TimeSpan.FromMinutes(5);
// Dead letter handling
options.DeadLetterQueueName = "my-queue-dlq";
options.MaxDequeueCount = 5; // Retries before DLQ
// Security
options.EnableEncryption = false;
// Debugging
options.EnableVerboseLogging = false;
options.EmptyQueueDelayMs = 1000;
});
Retry Policies
services.Configure<AzureProviderOptions>(options =>
{
options.RetryOptions = new AzureRetryOptions
{
MaxRetries = 3, // Retry attempts
Delay = TimeSpan.FromSeconds(1), // Initial delay
MaxDelay = TimeSpan.FromSeconds(10), // Max delay
Mode = RetryMode.Exponential // or Fixed
};
});
Health Checks
Registration
services.AddHealthChecks()
.AddAzureServiceBusQueue(
connectionString: "Endpoint=sb://...",
queueName: "my-queue",
name: "servicebus",
tags: new[] { "ready", "messaging" });
Custom Health Check
public class ServiceBusHealthCheck : IHealthCheck
{
private readonly AzureServiceBusHealthChecker _healthChecker;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
var result = await _healthChecker.CheckHealthAsync(cancellationToken);
return result.IsHealthy
? HealthCheckResult.Healthy()
: HealthCheckResult.Degraded(result.Description);
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Service Bus unreachable", ex);
}
}
}
Production Considerations
Scaling
Service Bus
- Use multiple processors with unique instance identifiers
- Enable sessions for ordered processing per session ID
- Use topics with subscriptions for pub/sub patterns
- Scale out with competing consumers on queues
Event Hubs
- Scale based on partition count (1 consumer per partition max)
- Use consumer groups for multiple applications
- Configure appropriate prefetch count for throughput
- Consider Capture for archival to storage
Storage Queues
- Simple horizontal scaling with multiple consumers
- Lower throughput than Service Bus (~2000 messages/sec per queue)
- Cost-effective for simple queue scenarios
Performance Tuning
Service Bus High-Throughput
services.Configure<AzureServiceBusOptions>(options =>
{
options.MaxConcurrentCalls = 32; // Increase concurrency
options.PrefetchCount = 100; // More prefetch
options.TransportType = ServiceBusTransportType.AmqpTcp; // Faster than WebSockets
});
Event Hubs High-Throughput
services.Configure<AzureEventHubOptions>(options =>
{
options.PrefetchCount = 500; // More prefetch
options.MaxBatchSize = 100; // Process in batches
});
Monitoring and Alerting
Key Azure Monitor metrics:
| Service | Metric | Alert Threshold |
|---|---|---|
| Service Bus | ActiveMessages |
> 10,000 |
| Service Bus | DeadLetteredMessages |
> 100 |
| Service Bus | ServerErrors |
> 0 |
| Event Hubs | IncomingMessages |
Baseline deviation |
| Event Hubs | ThrottledRequests |
> 0 |
| Storage Queues | QueueMessageCount |
> 10,000 |
Security Best Practices
- Use Managed Identity instead of connection strings
- Enable Private Endpoints to restrict network access
- Configure RBAC with least-privilege roles
- Enable diagnostic logging for audit trails
- Use Key Vault for secrets when connection strings are required
- Enable encryption for sensitive data
Cost Optimization
- Choose the right tier: Basic, Standard, or Premium for Service Bus
- Use Standard tier Event Hubs for most scenarios (Premium for high throughput)
- Storage Queues are cheapest for simple queue patterns
- Auto-delete idle resources to avoid costs
- Set appropriate message TTL to avoid accumulation
Troubleshooting
Common Issues
Connection Refused
Azure.Messaging.ServiceBus.ServiceBusException: The connection was refused
Solutions:
- Verify connection string format
- Check namespace exists and is accessible
- Verify firewall/network rules allow access
- For managed identity, verify RBAC role assignments
Unauthorized Access
Azure.Identity.AuthenticationFailedException: ManagedIdentityCredential authentication unavailable
Solutions:
- Enable managed identity on your Azure resource (App Service, VM, AKS)
- Assign correct RBAC roles to the identity
- For local development, use
DefaultAzureCredentialwith Azure CLI login
Queue Not Found
Azure.Messaging.ServiceBus.ServiceBusException: Entity not found
Solutions:
- Verify queue/topic name is correct (case-sensitive)
- Check entity exists in the namespace
- Verify connection string points to correct namespace
Message Lock Lost
Azure.Messaging.ServiceBus.ServiceBusException: The lock supplied is invalid
Solutions:
- Increase message lock duration in queue settings
- Process messages faster
- Use auto-renew lock feature
- Avoid long-running synchronous operations
Logging Configuration
{
"Logging": {
"LogLevel": {
"Excalibur.Dispatch.Transport.AzureServiceBus": "Debug",
"Azure.Messaging.ServiceBus": "Information",
"Azure.Messaging.EventHubs": "Information",
"Azure.Core": "Warning"
}
}
}
Debug Tips
- Enable Application Insights for distributed tracing
- Use Service Bus Explorer to inspect queues/topics
- Check Azure Monitor logs for service-side errors
- Test with Azure Portal to verify queue accessibility
- Enable diagnostic settings on Service Bus namespace
Complete Configuration Reference
Service Bus
services.Configure<AzureServiceBusOptions>(options =>
{
// Connection
options.Namespace = "mynamespace.servicebus.windows.net";
options.QueueName = "my-queue";
options.ConnectionString = null; // Or use connection string
options.TransportType = ServiceBusTransportType.AmqpTcp;
// Performance
options.MaxConcurrentCalls = 10;
options.PrefetchCount = 50;
// CloudEvents
options.CloudEventsMode = CloudEventsMode.Structured;
// Error handling
options.DeadLetterOnRejection = false;
// Security
options.EnableEncryption = false;
});
services.Configure<AzureProviderOptions>(options =>
{
// Authentication
options.UseManagedIdentity = true;
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.TenantId = "";
options.ClientId = "";
options.ClientSecret = "";
// Azure metadata
options.SubscriptionId = "";
options.ResourceGroup = "";
// Key Vault
options.KeyVaultUrl = null;
// Storage (for checkpointing)
options.StorageAccountName = "";
options.StorageAccountKey = "";
options.StorageAccountUri = null;
// Settings
options.MaxMessageSizeBytes = 262144; // 256 KB
options.EnableSessions = false;
options.PrefetchCount = 10;
// Retry
options.RetryOptions = new AzureRetryOptions
{
MaxRetries = 3,
Delay = TimeSpan.FromSeconds(1),
MaxDelay = TimeSpan.FromSeconds(10),
Mode = RetryMode.Exponential
};
});
Event Hubs
services.Configure<AzureEventHubOptions>(options =>
{
// Connection
options.ConnectionString = null;
options.FullyQualifiedNamespace = "mynamespace.servicebus.windows.net";
options.EventHubName = "my-eventhub";
options.ConsumerGroup = "$Default";
// Performance
options.PrefetchCount = 300;
options.MaxBatchSize = 100;
// Processing
options.StartingPosition = EventHubStartingPosition.Latest;
// Security
options.EnableEncryption = false;
options.EncryptionProviderName = null;
// Debugging
options.EnableVerboseLogging = false;
options.CustomProperties = new Dictionary<string, string>();
});
Storage Queues
services.Configure<AzureStorageQueueOptions>(options =>
{
// Connection
options.ConnectionString = null;
options.StorageAccountUri = new Uri("https://mystorageaccount.queue.core.windows.net/");
options.QueueName = "my-queue";
// Processing
options.MaxConcurrentMessages = 10;
options.MaxMessages = 10;
options.PollingInterval = TimeSpan.FromSeconds(1);
options.VisibilityTimeout = TimeSpan.FromMinutes(5);
options.EmptyQueueDelayMs = 1000;
// Dead letter
options.DeadLetterQueueName = null;
options.MaxDequeueCount = 5;
// Security
options.EnableEncryption = false;
options.EncryptionProviderName = null;
// Debugging
options.EnableVerboseLogging = false;
options.CustomProperties = new Dictionary<string, string>();
});
See Also
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Azure.Identity (>= 1.17.1)
- Azure.Messaging.EventGrid (>= 4.29.0)
- Azure.Messaging.EventHubs (>= 5.12.2)
- Azure.Messaging.EventHubs.Processor (>= 5.12.2)
- Azure.Messaging.ServiceBus (>= 7.20.1)
- Azure.Storage.Blobs (>= 12.25.1)
- Azure.Storage.Queues (>= 12.25.0)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Consul (>= 1.7.14.9)
- Cronos (>= 0.11.1)
- Excalibur.Dispatch (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Transport.Abstractions (>= 3.0.0-alpha.19)
- Grpc.Net.Client (>= 2.71.0)
- KubernetesClient (>= 17.0.14)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- Polly (>= 8.6.4)
- System.Threading.RateLimiting (>= 10.0.0)
- YamlDotNet (>= 16.3.0)
-
net8.0
- Azure.Identity (>= 1.17.1)
- Azure.Messaging.EventGrid (>= 4.29.0)
- Azure.Messaging.EventHubs (>= 5.12.2)
- Azure.Messaging.EventHubs.Processor (>= 5.12.2)
- Azure.Messaging.ServiceBus (>= 7.20.1)
- Azure.Storage.Blobs (>= 12.25.1)
- Azure.Storage.Queues (>= 12.25.0)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Consul (>= 1.7.14.9)
- Cronos (>= 0.11.1)
- Excalibur.Dispatch (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Transport.Abstractions (>= 3.0.0-alpha.19)
- Grpc.Net.Client (>= 2.71.0)
- KubernetesClient (>= 17.0.14)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- Polly (>= 8.6.4)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- System.IO.Pipelines (>= 10.0.0)
- System.Text.Json (>= 10.0.0)
- System.Threading.Channels (>= 10.0.0)
- System.Threading.RateLimiting (>= 10.0.0)
- YamlDotNet (>= 16.3.0)
-
net9.0
- Azure.Identity (>= 1.17.1)
- Azure.Messaging.EventGrid (>= 4.29.0)
- Azure.Messaging.EventHubs (>= 5.12.2)
- Azure.Messaging.EventHubs.Processor (>= 5.12.2)
- Azure.Messaging.ServiceBus (>= 7.20.1)
- Azure.Storage.Blobs (>= 12.25.1)
- Azure.Storage.Queues (>= 12.25.0)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Consul (>= 1.7.14.9)
- Cronos (>= 0.11.1)
- Excalibur.Dispatch (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Transport.Abstractions (>= 3.0.0-alpha.19)
- Grpc.Net.Client (>= 2.71.0)
- KubernetesClient (>= 17.0.14)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- Polly (>= 8.6.4)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- System.IO.Pipelines (>= 10.0.0)
- System.Text.Json (>= 10.0.0)
- System.Threading.Channels (>= 10.0.0)
- System.Threading.RateLimiting (>= 10.0.0)
- YamlDotNet (>= 16.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-alpha.19 | 34 | 2/26/2026 |