SaasSuite.Metering 26.3.3.2

dotnet add package SaasSuite.Metering --version 26.3.3.2
                    
NuGet\Install-Package SaasSuite.Metering -Version 26.3.3.2
                    
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="SaasSuite.Metering" Version="26.3.3.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SaasSuite.Metering" Version="26.3.3.2" />
                    
Directory.Packages.props
<PackageReference Include="SaasSuite.Metering" />
                    
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 SaasSuite.Metering --version 26.3.3.2
                    
#r "nuget: SaasSuite.Metering, 26.3.3.2"
                    
#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 SaasSuite.Metering@26.3.3.2
                    
#: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=SaasSuite.Metering&version=26.3.3.2
                    
Install as a Cake Addin
#tool nuget:?package=SaasSuite.Metering&version=26.3.3.2
                    
Install as a Cake Tool

SaasSuite.Metering

NuGet License: Apache-2.0 Platform

Usage tracking and metering system for multi-tenant SaaS applications with flexible aggregation and reporting capabilities.

Overview

SaasSuite.Metering provides comprehensive usage tracking for any metric in your SaaS application. Track API calls, storage consumption, compute time, or any custom metric, then aggregate and analyze usage data for billing, analytics, and quota enforcement.

Features

  • Event Recording: Track individual usage events with metadata
  • Flexible Metrics: Monitor any metric (API calls, storage, bandwidth, users, etc.)
  • Time-Range Queries: Retrieve usage data for specific periods
  • Data Aggregation: Summarize usage by hour, day, or month
  • Statistics: Calculate sum, average, min, max for each metric
  • Metadata Support: Attach key-value pairs to usage events
  • Configurable Retention: Automatic data cleanup after retention period
  • Thread-Safe Storage: Concurrent dictionary-based in-memory implementation

Installation

dotnet add package SaasSuite.Metering
dotnet add package SaasSuite.Core

Quick Start

1. Register Services

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSaasMetering(options =>
{
    options.DataRetentionDays = 90;
    options.EnableAutoAggregation = true;
    options.AggregationIntervalMinutes = 60;
});

2. Record Usage Events

using SaasSuite.Core.Interfaces;
using SaasSuite.Metering.Interfaces;

public class ApiController : ControllerBase
{
    private readonly IMeteringService _meteringService;
    private readonly ITenantAccessor _tenantAccessor;
    
    public ApiController(
        IMeteringService meteringService,
        ITenantAccessor tenantAccessor)
    {
        _meteringService = meteringService;
        _tenantAccessor = tenantAccessor;
    }
    
    [HttpGet("data")]
    public async Task<IActionResult> GetData()
    {
        var tenantId = _tenantAccessor.TenantContext?.TenantId;
        
        // Record API call
        await _meteringService.RecordUsageAsync(
            tenantId,
            "api-calls",
            value: 1);
        
        var data = await FetchDataAsync();
        return Ok(data);
    }
}

Usage Examples

Track API Calls

public class ApiCallMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMeteringService _meteringService;
    
    public ApiCallMiddleware(
        RequestDelegate next,
        IMeteringService meteringService)
    {
        _next = next;
        _meteringService = meteringService;
    }
    
    public async Task InvokeAsync(HttpContext context, ITenantAccessor tenantAccessor)
    {
        await _next(context);
        
        // Record API call after request completes
        var tenantId = tenantAccessor.TenantContext?.TenantId;
        if (tenantId != null)
        {
            await _meteringService.RecordUsageAsync(
                tenantId,
                "api-calls",
                value: 1,
                metadata: new Dictionary<string, string>
                {
                    ["endpoint"] = context.Request.Path,
                    ["method"] = context.Request.Method,
                    ["status_code"] = context.Response.StatusCode.ToString()
                });
        }
    }
}

Track Storage Usage

public class StorageService
{
    private readonly IMeteringService _meteringService;
    private readonly ITenantAccessor _tenantAccessor;
    
    public async Task UploadFileAsync(Stream fileStream, string fileName)
    {
        var tenantId = _tenantAccessor.TenantContext.TenantId;
        var fileSizeBytes = fileStream.Length;
        
        // Save file
        await SaveFileAsync(fileStream, fileName);
        
        // Record storage usage in GB
        var fileSizeGB = fileSizeBytes / (1024.0 * 1024.0 * 1024.0);
        await _meteringService.RecordUsageAsync(
            tenantId,
            "storage-gb",
            value: fileSizeGB,
            metadata: new Dictionary<string, string>
            {
                ["file_name"] = fileName,
                ["file_size_bytes"] = fileSizeBytes.ToString()
            });
    }
}

Track Compute Time

public class JobProcessor
{
    private readonly IMeteringService _meteringService;
    
    public async Task ProcessJobAsync(TenantId tenantId, Job job)
    {
        var startTime = DateTime.UtcNow;
        
        try
        {
            // Process job
            await ExecuteJobAsync(job);
        }
        finally
        {
            var duration = DateTime.UtcNow - startTime;
            
            // Record compute time in minutes
            await _meteringService.RecordUsageAsync(
                tenantId,
                "compute-minutes",
                value: duration.TotalMinutes,
                metadata: new Dictionary<string, string>
                {
                    ["job_id"] = job.Id,
                    ["job_type"] = job.Type
                });
        }
    }
}

Track Active Users

public class UserActivityTracker
{
    private readonly IMeteringService _meteringService;
    
    public async Task RecordUserActivityAsync(TenantId tenantId, string userId)
    {
        await _meteringService.RecordUsageAsync(
            tenantId,
            "active-users",
            value: 1,
            metadata: new Dictionary<string, string>
            {
                ["user_id"] = userId
            });
    }
}

Querying Usage Data

Get Usage for Time Range

public class UsageReportController : ControllerBase
{
    private readonly IMeteringService _meteringService;
    
    [HttpGet("usage")]
    public async Task<IActionResult> GetUsage(
        string tenantId,
        DateTime startDate,
        DateTime endDate)
    {
        var usage = await _meteringService.GetUsageAsync(
            new TenantId(tenantId),
            startDate,
            endDate);
        
        return Ok(usage);
    }
}

Get Usage for Specific Metric

[HttpGet("usage/api-calls")]
public async Task<IActionResult> GetApiCallUsage(string tenantId)
{
    var startDate = DateTime.UtcNow.AddDays(-30);
    var endDate = DateTime.UtcNow;
    
    var usage = await _meteringService.GetUsageAsync(
        new TenantId(tenantId),
        startDate,
        endDate,
        metricName: "api-calls");
    
    var totalCalls = usage.Sum(u => u.Value);
    
    return Ok(new
    {
        totalCalls,
        period = "last-30-days",
        events = usage.Count
    });
}

Get Current Month Usage

[HttpGet("usage/current-month")]
public async Task<IActionResult> GetCurrentMonthUsage(string tenantId)
{
    var usage = await _meteringService.GetCurrentMonthUsageAsync(
        new TenantId(tenantId));
    
    // Group by metric
    var grouped = usage
        .GroupBy(u => u.MetricName)
        .Select(g => new
        {
            metric = g.Key,
            total = g.Sum(u => u.Value),
            count = g.Count()
        });
    
    return Ok(grouped);
}

Data Aggregation

Aggregate by Day

public class DailyUsageReporter
{
    private readonly IMeteringService _meteringService;
    
    public async Task<List<UsageAggregate>> GetDailyUsageAsync(
        TenantId tenantId,
        string metricName)
    {
        var startDate = DateTime.UtcNow.AddDays(-30);
        var endDate = DateTime.UtcNow;
        
        var aggregates = await _meteringService.AggregateUsageAsync(
            tenantId,
            startDate,
            endDate,
            AggregationPeriod.Daily,
            metricName);
        
        return aggregates;
    }
}

Aggregate by Hour

public async Task<IActionResult> GetHourlyApiCalls(string tenantId)
{
    var startDate = DateTime.UtcNow.AddHours(-24);
    var endDate = DateTime.UtcNow;
    
    var aggregates = await _meteringService.AggregateUsageAsync(
        new TenantId(tenantId),
        startDate,
        endDate,
        AggregationPeriod.Hourly,
        "api-calls");
    
    return Ok(aggregates.Select(a => new
    {
        hour = a.PeriodStart,
        calls = a.Sum,
        average = a.Average,
        min = a.Min,
        max = a.Max
    }));
}

Aggregate by Month

public async Task<IActionResult> GetMonthlyStorageUsage(string tenantId)
{
    var startDate = DateTime.UtcNow.AddMonths(-12);
    var endDate = DateTime.UtcNow;
    
    var aggregates = await _meteringService.AggregateUsageAsync(
        new TenantId(tenantId),
        startDate,
        endDate,
        AggregationPeriod.Monthly,
        "storage-gb");
    
    return Ok(aggregates.Select(a => new
    {
        month = a.PeriodStart.ToString("yyyy-MM"),
        totalGB = a.Sum,
        averageGB = a.Average
    }));
}

Integration with Billing

using SaasSuite.Billing.Interfaces;

public class UsageBasedBillingService
{
    private readonly IMeteringService _meteringService;
    private readonly IBillingOrchestrator _billingOrchestrator;
    
    public async Task GenerateMonthlyInvoiceAsync(TenantId tenantId)
    {
        // Get current month usage
        var usage = await _meteringService.GetCurrentMonthUsageAsync(tenantId);
        
        // Calculate charges
        var charges = new Dictionary<string, decimal>
        {
            ["api-calls"] = CalculateApiCallCharges(usage),
            ["storage-gb"] = CalculateStorageCharges(usage),
            ["bandwidth-gb"] = CalculateBandwidthCharges(usage)
        };
        
        // Generate invoice with usage charges
        var invoice = await _billingOrchestrator.GenerateInvoiceAsync(
            tenantId,
            new BillingCycle
            {
                StartDate = GetMonthStart(),
                EndDate = GetMonthEnd()
            });
        
        return invoice;
    }
    
    private decimal CalculateApiCallCharges(List<UsageEvent> usage)
    {
        var totalCalls = usage
            .Where(u => u.MetricName == "api-calls")
            .Sum(u => u.Value);
        
        // $0.001 per API call
        return totalCalls * 0.001m;
    }
    
    private decimal CalculateStorageCharges(List<UsageEvent> usage)
    {
        var avgStorageGB = usage
            .Where(u => u.MetricName == "storage-gb")
            .Average(u => u.Value);
        
        // $0.10 per GB-month
        return avgStorageGB * 0.10m;
    }
}

Configuration Options

public class MeteringOptions
{
    // Data retention period in days
    public int DataRetentionDays { get; set; } = 90;
    
    // Enable automatic background aggregation
    public bool EnableAutoAggregation { get; set; } = false;
    
    // Interval for auto-aggregation in minutes
    public int AggregationIntervalMinutes { get; set; } = 60;
    
    // Optional metric whitelist (null = all metrics allowed)
    public List<string>? AllowedMetrics { get; set; }
}

Core Models

UsageEvent

public class UsageEvent
{
    public string EventId { get; set; }
    public TenantId TenantId { get; set; }
    public string MetricName { get; set; }
    public double Value { get; set; }
    public DateTime Timestamp { get; set; }
    public Dictionary<string, string> Metadata { get; set; }
}

UsageAggregate

public class UsageAggregate
{
    public TenantId TenantId { get; set; }
    public string MetricName { get; set; }
    public DateTime PeriodStart { get; set; }
    public DateTime PeriodEnd { get; set; }
    public double Sum { get; set; }
    public double Average { get; set; }
    public double Min { get; set; }
    public double Max { get; set; }
    public int EventCount { get; set; }
}

Storage

The default implementation uses thread-safe in-memory storage. For production, implement IMeteringStore backed by:

  • Time-series database (InfluxDB, TimescaleDB)
  • NoSQL database (MongoDB, DynamoDB)
  • Data warehouse (BigQuery, Redshift)
  • Event streaming (Kafka, Event Hubs)

Best Practices

  1. Record Immediately: Track usage in real-time, not in batches
  2. Use Metadata: Add context for better analysis
  3. Aggregate for Reporting: Use aggregates instead of raw events
  4. Set Retention: Configure appropriate retention periods
  5. Monitor Performance: Track metering overhead
  6. Validate Metrics: Use metric whitelists in production

License

This package is licensed under the Apache License 2.0. See the LICENSE file in the repository root for details.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on SaasSuite.Metering:

Package Downloads
SaasSuite.Subscriptions

Subscription management for multi-tenant SaaS applications

SaasSuite.Quotas

Quota management and enforcement for multi-tenant SaaS applications

SaasSuite.Billing

Billing and invoicing for multi-tenant SaaS applications

SaasSuite

Convenience meta-package that bundles the core SaaS building blocks. Includes SaasSuite.Core, SaasSuite.Features, SaasSuite.Seats, SaasSuite.Quotas, SaasSuite.Metering, SaasSuite.Billing, SaasSuite.Subscriptions, SaasSuite.Audit, and SaasSuite.Migration. Integrations live in separate packages and are not included.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
26.3.3.2 189 3/3/2026