ApiFeatures.Notifications.Apis
9.2.4
dotnet add package ApiFeatures.Notifications.Apis --version 9.2.4
NuGet\Install-Package ApiFeatures.Notifications.Apis -Version 9.2.4
<PackageReference Include="ApiFeatures.Notifications.Apis" Version="9.2.4" />
<PackageVersion Include="ApiFeatures.Notifications.Apis" Version="9.2.4" />
<PackageReference Include="ApiFeatures.Notifications.Apis" />
paket add ApiFeatures.Notifications.Apis --version 9.2.4
#r "nuget: ApiFeatures.Notifications.Apis, 9.2.4"
#:package ApiFeatures.Notifications.Apis@9.2.4
#addin nuget:?package=ApiFeatures.Notifications.Apis&version=9.2.4
#tool nuget:?package=ApiFeatures.Notifications.Apis&version=9.2.4
ApiFeatures.Notifications.Apis
I. Overview
ApiFeatures.Notifications.Apis is a comprehensive notification module for ASP.NET Core applications that provides real-time notification delivery capabilities with support for both user-specific and channel-based broadcasting.
Key Features
- Real-time Notifications: Server-Sent Events (SSE) for live notification delivery
- Dual Notification Types:
- Persistent Notifications: Saved to database for historical retrieval
- Non-Persistent Notifications: Transient messages for real-time events (typing indicators, presence updates)
- User & Channel Support:
- Send notifications to specific users
- Broadcast notifications to all users in a channel
- Flexible Architecture:
- In-memory or MongoDB storage options
- Optional Redis pub/sub for horizontal scaling across multiple instances
- Validation: Built-in FluentValidation rules for all DTOs
- Scalable: Supports both single-server and multi-server deployments
Architecture
???????????????
? Client ?
? (SSE) ?
???????????????
?
?
???????????????????????????????????
? ASP.NET Core Application ?
? ???????????????????????????? ?
? ? Notification Endpoints ? ?
? ???????????????????????????? ?
? ? ?
? ???????????????????????????? ?
? ? Notification Service ? ?
? ???????????????????????????? ?
? ? ?
? ???????????????????????????? ?
? ? Background Services ? ?
? ? � Redis Publisher ? ?
? ? � Redis Subscriber ? ?
? ? � Default Broadcaster ? ?
? ???????????????????????????? ?
? ? ?
? ???????????????????????????? ?
? ? SSE Session Manager ? ?
? ???????????????????????????? ?
???????????????????????????????????
? ?
? ?
??????????????? ???????????????
? MongoDB ? ? Redis ?
? (Optional) ? ? (Optional) ?
??????????????? ???????????????
II. Installation
1. Add Package Reference
Add the ApiFeatures.Notifications.Apis package to your ASP.NET Core project.
2. Configure Services
In your Program.cs, configure the notification feature:
using ApiFeatures.Notifications.Apis.Extensions;
using ApiFeatures.Notifications.Apis.Models;
using ApiFeatures.Notifications.Apis.Services.Interfaces;
using IotVn.CoreFeatures.Services.Abstractions;
var builder = WebApplication.CreateBuilder(args);
// Register JsonTool (required for SSE serialization)
builder.Services.AddSingleton<IJsonTool, JsonTool>();
// Configure Notification Feature
var notificationOptions = new AddNotificationFeatureOptions();
// 1. Configure Database
notificationOptions.WithDatabaseOptions(serviceProvider =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var options = configuration.GetSection("Notifications").Get<NotificationOptions>();
// Option A: MongoDB
if (options?.MongoDatabase != null)
{
return options.MongoDatabase;
}
// Option B: In-Memory (for development/testing)
if (options?.InMemoryDatabase != null)
{
return options.InMemoryDatabase;
}
throw new InvalidOperationException("No database options configured");
});
// 2. Configure User Service (for authorization)
notificationOptions.WithUserService(serviceProvider =>
{
// Return your implementation of IUserService
return new YourUserService();
});
// 3. Optional: Configure Redis for multi-instance deployments
var config = builder.Configuration.GetSection("Notifications").Get<NotificationOptions>();
if (config?.Sse?.RedisDatabase != null)
{
notificationOptions.WithRedis(config.Sse.RedisDatabase);
}
// Register the feature
builder.Services.AddNotificationFeature(notificationOptions);
var app = builder.Build();
// Map Notification Endpoints
app.AddNotificationEndpoints(new AddNotificationEndpointsOptions
{
// Optional: Add authorization policy
AuthorizationPolicyHandler = () => "YourPolicyName"
});
app.Run();
3. Implement IUserService
Create your user service implementation:
using ApiFeatures.Notifications.Apis.Interfaces;
using ApiFeatures.Notifications.Apis.Services.Interfaces;
using Microsoft.AspNetCore.Http;
public class UserService : IUserService
{
public Task<IUserContext> GetContextAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
{
// Extract user information from HttpContext (e.g., from JWT claims)
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
return Task.FromResult<IUserContext>(new UserContext { UserId = userId });
}
public Task ValidatePublishNotificationAbilityAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
{
// Implement authorization logic for publishing notifications
return Task.CompletedTask;
}
public Task ValidatePublishChannelNotificationAbilityAsync(HttpContext httpContext, string channelCode, CancellationToken cancellationToken = default)
{
// Implement authorization logic for publishing to channels
return Task.CompletedTask;
}
public Task ValidateDeleteNotificationAbilityAsync(HttpContext httpContext, string userId, CancellationToken cancellationToken = default)
{
// Implement authorization logic for deleting notifications
return Task.CompletedTask;
}
public Task ValidateNotificationRetrievalAbilityAsync(HttpContext httpContext, string userId, CancellationToken cancellationToken = default)
{
// Implement authorization logic for retrieving notifications
return Task.CompletedTask;
}
}
III. Configuration Options
AddNotificationFeatureOptions Methods
WithDatabaseOptions(Func<IServiceProvider, DatabaseOptions>) [REQUIRED]
Configures the database storage for notifications.
Factory version:
options.WithDatabaseOptions(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return config.GetSection("Notifications:MongoDatabase").Get<MongoDatabaseOptions>();
});
Generic version:
options.WithDatabaseOptions<MongoDatabaseOptions>();
options.WithDatabaseOptions<InMemoryDatabaseOptions>();
Parameters:
MongoDatabaseOptions:ConnectionString(string, required): MongoDB connection string with optionaluuidRepresentation=standardDatabaseName(string, required): Database name (can be in connection string or property)Collection.Notifications(string, optional): Collection name (default: "Notifications")Collection.UserChannels(string, optional): Collection name (default: "UserChannels")
InMemoryDatabaseOptions: No configuration needed (for testing/development)
Lifetime: Singleton (keyed with ServiceKeys.Default)
WithUserService(Func<IServiceProvider, IUserService>) [REQUIRED]
Registers the user service for extracting user context from HTTP requests.
Factory version:
options.WithUserService(sp => sp.GetRequiredService<MyUserService>());
options.WithUserService(sp => new MyUserService());
Generic version:
options.WithUserService<MyUserService>();
Lifetime: Scoped (keyed with ServiceKeys.Default)
WithJsonTool(Func<IServiceProvider, IJsonTool>) [REQUIRED]
Registers the JSON serialization tool for metadata and SSE messages.
Factory version:
options.WithJsonTool(sp => sp.GetRequiredService<MyJsonTool>());
options.WithJsonTool(sp => new MyJsonTool());
Generic version:
options.WithJsonTool<MyJsonTool>();
Lifetime: Singleton (keyed with ServiceKeys.Default)
WithBusinessLogic(Func<IServiceProvider, INotificationBusinessLogic>) [OPTIONAL]
Registers custom business logic for notification authorization and validation.
Factory version:
options.WithBusinessLogic(sp => sp.GetRequiredService<MyNotificationBusinessLogic>());
Generic version:
options.WithBusinessLogic<MyNotificationBusinessLogic>();
Lifetime: Scoped (keyed with ServiceKeys.Default)
WithRedis(RedisOptions) [OPTIONAL]
Configures Redis pub/sub for horizontal scaling across multiple application instances.
options.WithRedis(new RedisOptions
{
ConnectionString = "redis://localhost:6379?abortConnect=false"
});
Parameters:
ConnectionString(string, required): Redis connection string (URI or legacy format)
Lifetime: Singleton (IConnectionMultiplexer)
IV. Environment Variables
Configuration Structure
The notification module is configured through the Notifications section in appsettings.json:
{
"Notifications": {
"MongoDatabase": {
"ConnectionString": "mongodb://localhost:27017?authSource=admin",
"DatabaseName": "your-database-name",
"Collection": {
"Notifications": "Notifications",
"UserChannels": "UserChannels"
}
},
"Sse": {
"RedisDatabase": {
"ConnectionString": "redis://localhost:6379?abortConnect=false"
}
}
}
}
Configuration Options
Database Options
MongoDB Configuration (Persistent Storage):
"MongoDatabase": {
"ConnectionString": "mongodb://localhost:27017?authSource=admin",
"DatabaseName": "your-database-name",
"Collection": {
"Notifications": "Notifications",
"UserChannels": "UserChannels"
}
}
| Property | Type | Required | Description |
|---|---|---|---|
ConnectionString |
string | Yes | MongoDB connection string |
DatabaseName |
string | Yes | Name of the database to use |
Collection.Notifications |
string | No | Collection name for notifications (default: "Notifications") |
Collection.UserChannels |
string | No | Collection name for user-channel mappings (default: "UserChannels") |
In-Memory Configuration (Development/Testing):
"InMemoryDatabase": {}
SSE/Redis Configuration
Redis Configuration (Optional - for multi-instance deployments):
"Sse": {
"RedisDatabase": {
"ConnectionString": "redis://localhost:6379?abortConnect=false"
}
}
| Property | Type | Required | Description |
|---|---|---|---|
ConnectionString |
string | Yes | Redis connection string (URI or legacy format) |
Redis Connection String Examples:
// Simple local Redis
"redis://localhost:6379?abortConnect=false"
// With password
"redis://:mypassword@localhost:6379?abortConnect=false"
// With username and password
"redis://default:mypassword@localhost:6379?abortConnect=false"
// With database selection
"redis://localhost:6379/1?abortConnect=false"
// Remote with authentication
"redis://:abcde12345-@mini-pc.example.com:9032/6?abortConnect=false"
// With SSL (rediss://)
"rediss://localhost:6380?abortConnect=false"
// Legacy format (comma-separated key=value)
"localhost:6379,password=secret,defaultDatabase=1,abortConnect=false"
Important: Always include
abortConnect=falseto allow the application to start even if Redis is unavailable.
Complete Configuration Example
appsettings.json (MongoDB + Redis):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Notifications": {
"MongoDatabase": {
"ConnectionString": "mongodb://localhost:27017?authSource=admin",
"DatabaseName": "notifications-db",
"Collection": {
"Notifications": "Notifications",
"UserChannels": "UserChannels"
}
},
"Sse": {
"RedisDatabase": {
"ConnectionString": "redis://localhost:6379?abortConnect=false"
}
}
}
}
appsettings.Development.json (In-Memory only):
{
"Notifications": {
"InMemoryDatabase": {}
}
}
Environment-Specific Configuration
Use environment variables to override settings:
# Windows
set Notifications__MongoDatabase__ConnectionString=mongodb://prod-server:27017
set Notifications__Sse__RedisDatabase__ConnectionString=redis://prod-redis:6379
# Linux/macOS
export Notifications__MongoDatabase__ConnectionString=mongodb://prod-server:27017
export Notifications__Sse__RedisDatabase__ConnectionString=redis://prod-redis:6379
Or use User Secrets for development:
dotnet user-secrets set "Notifications:MongoDatabase:ConnectionString" "mongodb://localhost:27017"
dotnet user-secrets set "Notifications:Sse:RedisDatabase:ConnectionString" "redis://localhost:6379"
IV. API Endpoints
Notification Management
Create User Notification
POST /api/notification
Content-Type: application/json
{
"code": "ORDER_PLACED",
"category": "Order",
"userId": "user123",
"metadata": {
"orderId": "ORD-123",
"amount": "99.99"
},
"isPersistent": true
}
Create Channel Notification
POST /api/notification/by-channel
Content-Type: application/json
{
"channelCode": "SALES_TEAM",
"code": "NEW_LEAD",
"category": "Sales",
"metadata": {
"leadId": "LEAD-456"
},
"isPersistent": true
}
Get Notification by ID
GET /api/notification/{id}
Delete Notification
DELETE /api/notification/{id}
Server-Sent Events (SSE)
Connect to User Notifications Stream
GET /api/sse/user-notifications
Accept: text/event-stream
Connect to Channel Notifications Stream
GET /api/sse/channel-notifications?code=SALES_TEAM
Accept: text/event-stream
SSE Event Format:
event: connected
data: {"message":"Connected to notification stream for user user123"}
event: notification
data: {"id":"018d1234-5678-9abc-def0-123456789abc","type":0,"userId":"user123"}
: keep-alive
V. Usage Examples
JavaScript Client (SSE)
// Connect to user notifications
const eventSource = new EventSource('/api/sse/user-notifications');
eventSource.addEventListener('connected', (e) => {
console.log('Connected:', JSON.parse(e.data));
});
eventSource.addEventListener('notification', (e) => {
const notification = JSON.parse(e.data);
console.log('Received notification:', notification);
// Handle persistent notification
if (notification.type === 0) {
console.log('Persistent notification ID:', notification.id);
}
// Handle non-persistent notification
else if (notification.type === 1) {
console.log('Non-persistent notification:', notification.code);
}
});
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
};
// Connect to channel notifications
const channelSource = new EventSource('/api/sse/channel-notifications?code=SALES_TEAM');
C# Client
// Send persistent notification
var notification = new CreateNotificationPayloadDto
{
Code = "ORDER_SHIPPED",
Category = "Order",
UserId = "user123",
Metadata = new Dictionary<string, string>
{
["orderId"] = "ORD-789",
["trackingNumber"] = "TRACK123"
},
IsPersistent = true
};
var response = await httpClient.PostAsJsonAsync("/api/notification", notification);
// Send non-persistent (transient) notification
var typingIndicator = new CreateNotificationPayloadDto
{
Code = "USER_TYPING",
Category = "RealTime",
UserId = "user456",
Metadata = new Dictionary<string, string>(),
IsPersistent = false // Won't be saved to database
};
await httpClient.PostAsJsonAsync("/api/notification", typingIndicator);
// Broadcast to channel
var channelNotification = new CreateChannelNotificationPayloadDto
{
ChannelCode = "SUPPORT_TEAM",
Code = "NEW_TICKET",
Category = "Support",
Metadata = new Dictionary<string, string>
{
["ticketId"] = "TICKET-123"
},
IsPersistent = true
};
await httpClient.PostAsJsonAsync("/api/notification/by-channel", channelNotification);
VI. Validation Rules
Notification Code
- Must start and end with alphanumeric character
- Can contain letters, numbers, underscores, and hyphens
- Maximum length: 100 characters
- Examples:
ORDER_PLACED,USER_LOGGED-IN,ALERT123
Category
- Must start and end with alphanumeric character
- Can contain letters, numbers, underscores, and hyphens
- Maximum length: 100 characters
- Examples:
Order,User_Auth,System-Alert
Channel Code
- Must start with alphabet character
- Must end with alphanumeric character
- Can only contain letters, numbers, and underscores
- Maximum length: 128 characters
- Examples:
SALES_TEAM,Support,CHANNEL01
VII. Advanced Features
Persistent vs Non-Persistent Notifications
Persistent Notifications (isPersistent: true):
- Saved to database
- Can be retrieved later via API
- Includes notification ID
- Use for: Orders, alerts, messages
Non-Persistent Notifications (isPersistent: false):
- Only broadcast via SSE
- Not saved to database
- No notification ID
- Use for: Typing indicators, presence updates, temporary status
Channel-Based Broadcasting
Channels allow you to group users and broadcast notifications to all members:
- User subscribes to channel: When a user connects to
/api/sse/channel-notifications?code=CHANNEL_CODE, they're automatically registered to that channel - Broadcast to channel: Send POST to
/api/notification/by-channelwith the channel code - All subscribed users receive notification: The system creates individual notifications for each user in the channel
Horizontal Scaling
For multi-instance deployments, configure Redis to synchronize notifications across servers:
- Each application instance connects to the same Redis server
- When a notification is created, it's published to Redis
- All instances receive the notification and broadcast to their connected SSE clients
- Users can connect to any instance and receive all notifications
VIII. Logging
The module provides detailed logging at different levels:
Information Level:
- Background service lifecycle (started/stopped)
- SSE session registration/unregistration
- Redis connection status
Debug Level:
- Notification creation and broadcasting
- Individual notification routing
Error Level:
- Failed notification broadcasts
- Redis connection errors
- Database errors
Log messages include notification type-specific information:
- Persistent: Logs notification ID
- Non-Persistent: Logs notification code
IX. Troubleshooting
Common Issues
1. SSE Connection Closes Immediately
- Check that your reverse proxy supports SSE
- Ensure buffering is disabled for SSE endpoints
- Verify authorization is configured correctly
2. Notifications Not Received on Other Instances
- Verify Redis is configured and accessible
- Check Redis connection string format
- Ensure
abortConnect=falseis in the connection string
3. Database Connection Errors
- Verify MongoDB connection string
- Check database permissions
- Ensure database and collections exist
4. Channel Notifications Not Delivered
- Verify users are subscribed to the channel via SSE endpoint
- Check that channel code matches exactly
- Ensure
ChannelCodevalidation rules are met
Debug Checklist
- Enable Debug logging:
"LogLevel": { "Default": "Debug" } - Check background service logs for startup messages
- Verify SSE connections are established (look for "SSE session registered" logs)
- Monitor Redis pub/sub activity (if using Redis)
- Check database for saved notifications (if persistent)
XII. License
[Specify your license here]
XI. Contributing
[Add contribution guidelines if applicable]
XIV. Support
For issues, questions, or contributions, please contact [your support channel].
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- BusinessFeatures.Cores (>= 9.0.1)
- FluentValidation (>= 11.9.0)
- IotVn.CoreFeatures (>= 9.1.0)
- Microsoft.AspNetCore.OpenApi (>= 9.0.0)
- MongoDB.Driver (>= 3.5.2)
- StackExchange.Redis (>= 2.8.16)
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 |
|---|---|---|
| 9.2.4 | 32 | 5/7/2026 |