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
                    
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="ApiFeatures.Notifications.Apis" Version="9.2.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ApiFeatures.Notifications.Apis" Version="9.2.4" />
                    
Directory.Packages.props
<PackageReference Include="ApiFeatures.Notifications.Apis" />
                    
Project file
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 ApiFeatures.Notifications.Apis --version 9.2.4
                    
#r "nuget: ApiFeatures.Notifications.Apis, 9.2.4"
                    
#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 ApiFeatures.Notifications.Apis@9.2.4
                    
#: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=ApiFeatures.Notifications.Apis&version=9.2.4
                    
Install as a Cake Addin
#tool nuget:?package=ApiFeatures.Notifications.Apis&version=9.2.4
                    
Install as a Cake Tool

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 optional uuidRepresentation=standard
    • DatabaseName (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=false to 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:

  1. User subscribes to channel: When a user connects to /api/sse/channel-notifications?code=CHANNEL_CODE, they're automatically registered to that channel
  2. Broadcast to channel: Send POST to /api/notification/by-channel with the channel code
  3. 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:

  1. Each application instance connects to the same Redis server
  2. When a notification is created, it's published to Redis
  3. All instances receive the notification and broadcast to their connected SSE clients
  4. 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=false is 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 ChannelCode validation rules are met

Debug Checklist

  1. Enable Debug logging: "LogLevel": { "Default": "Debug" }
  2. Check background service logs for startup messages
  3. Verify SSE connections are established (look for "SSE session registered" logs)
  4. Monitor Redis pub/sub activity (if using Redis)
  5. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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