AbsoluteAlgorithm.Api.Common 1.0.0-dev.23

This is a prerelease version of AbsoluteAlgorithm.Api.Common.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package AbsoluteAlgorithm.Api.Common --version 1.0.0-dev.23
                    
NuGet\Install-Package AbsoluteAlgorithm.Api.Common -Version 1.0.0-dev.23
                    
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="AbsoluteAlgorithm.Api.Common" Version="1.0.0-dev.23" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AbsoluteAlgorithm.Api.Common" Version="1.0.0-dev.23" />
                    
Directory.Packages.props
<PackageReference Include="AbsoluteAlgorithm.Api.Common" />
                    
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 AbsoluteAlgorithm.Api.Common --version 1.0.0-dev.23
                    
#r "nuget: AbsoluteAlgorithm.Api.Common, 1.0.0-dev.23"
                    
#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 AbsoluteAlgorithm.Api.Common@1.0.0-dev.23
                    
#: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=AbsoluteAlgorithm.Api.Common&version=1.0.0-dev.23&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=AbsoluteAlgorithm.Api.Common&version=1.0.0-dev.23&prerelease
                    
Install as a Cake Tool

AbsoluteAlgorithm.Api.Common

AbsoluteAlgorithm.Api.Common is a reusable ASP.NET Core Web API foundation library. It is designed to be installed as a NuGet package and then switched on through a single ApplicationConfiguration object. Once registered, it wires up a consistent API pipeline for:

  • relational database access with keyed repositories and request-scoped transactions
  • provider-based object storage
  • named HttpClient registrations with resilience
  • JWT and cookie authentication
  • authorization policies and API key authorization
  • CSRF protection for cookie-based flows
  • API versioning
  • NSwag / OpenAPI / Swagger UI
  • request idempotency
  • webhook signature validation
  • rate limiting
  • health checks
  • standardized error and response contracts
  • request metadata normalization
  • spreadsheet formula sanitization for API input and CSV/export scenarios
  • utilities for hashing, encryption, tokens, ETags, optimistic concurrency, files, JSON, claims, compression, reflection, enums, and HTTP helpers

This README is intentionally dense. It is meant to act as the package manual for consumers who install this library as a NuGet package and need to understand everything the library exposes and how to wire it correctly.

Table of Contents

What the Package Gives You

At a practical level, this package exists to keep your API projects from rewriting the same infrastructure over and over again.

When you install the package and call:

builder.RegisterAbsoluteWebApplicationBuilder(appConfig);
app.UseAbsolutePipeline(appConfig);

the library takes responsibility for a large amount of startup and runtime plumbing. It validates configuration up front, registers only the features you enabled, and gives you a consistent runtime model.

You keep control of your own controllers, endpoints, entities, DTOs, and business logic. The library handles the cross-cutting parts.

NuGet Installation

Install the package:

dotnet add package AbsoluteAlgorithm.Api.Common

If you are consuming a prerelease package:

dotnet add package AbsoluteAlgorithm.Api.Common --prerelease

Package ID:

AbsoluteAlgorithm.Api.Common

Supported Runtime and Dependencies

The project targets:

net10.0

The package internally integrates with the following platform pieces and libraries:

  • Microsoft.AspNetCore.App
  • Dapper
  • Npgsql
  • Microsoft.Data.SqlClient
  • Polly
  • NSwag
  • Asp.Versioning.Mvc
  • Asp.Versioning.Mvc.ApiExplorer
  • NLog.Web.AspNetCore
  • CsvHelper
  • AWSSDK.S3
  • Azure.Storage.Blobs
  • Google.Cloud.Storage.V1
  • Minio
  • multiple health check packages for SQL Server, PostgreSQL, S3, MinIO, Azure Blob, and GCP storage

You do not need to manually register those integrations in your API project if you are using this package as intended. The package already wraps them.

How to Wire the Library

The integration model is:

  1. Build an ApplicationConfiguration object.
  2. Ensure required environment variables exist.
  3. Call builder.RegisterAbsoluteWebApplicationBuilder(appConfig).
  4. Build the app.
  5. Call app.UseAbsolutePipeline(appConfig).
  6. Add your controllers and use the keyed services the library registered.

This library is controller-oriented. RegisterAbsoluteWebApplicationBuilder already calls AddControllers(...), configures model binding, installs the validation filter, and configures JSON options.

Minimal Program.cs Example

using Common.Extensions;
using Common.Enums;
using Common.Models.Auth;
using Common.Models.Configuration;
using Common.Models.Database;
using Common.Models.Documentation;
using Common.Models.Health;
using Common.Models.Http;
using Common.Models.Idempotency;
using Common.Models.RateLimit;
using Common.Models.Resilience;
using Common.Models.Storage;
using Common.Models.Webhooks;

var builder = WebApplication.CreateBuilder(args);

var appConfig = new ApplicationConfiguration
{
    UseRelationalDatabase = true,
    DatabasePolicies =
    [
        new DatabasePolicy
        {
            Name = "primary",
            DatabaseProvider = DatabaseProvider.PostgreSQL,
            ConnectionStringName = "PRIMARY_DB_CONNECTION",
            InitializeDatabase = false,
            InitializeAuditTable = false,
            CommandTimeoutSeconds = 30,
            MaxPoolSize = 100,
            MinPoolSize = 10,
            ResiliencePolicy = new ResiliencePolicy
            {
                Enabled = true,
                Retry = new RetryResiliencePolicy
                {
                    Enabled = true,
                    MaxRetryAttempts = 3,
                    DelayStrategy = RetryDelayStrategy.Exponential,
                    DelayMilliseconds = 200,
                    BackoffMultiplier = 2
                },
                Timeout = new TimeoutResiliencePolicy
                {
                    Enabled = true,
                    TimeoutSeconds = 30
                }
            }
        }
    ],
    UseStorage = true,
    StoragePolicies =
    [
        new StoragePolicy
        {
            Name = "files",
            StorageProvider = StorageProvider.S3,
            ConnectionStringName = "S3_CONNECTION",
            BucketName = "my-app-files"
        }
    ],
    HttpClientPolicies =
    [
        new HttpClientPolicy
        {
            Name = "github",
            BaseAddress = "https://api.github.com/",
            TimeoutSeconds = 30,
            DefaultHeaders = new Dictionary<string, string>
            {
                ["User-Agent"] = "my-api"
            },
            ResiliencePolicy = new ResiliencePolicy
            {
                Enabled = true,
                Retry = new RetryResiliencePolicy
                {
                    Enabled = true,
                    MaxRetryAttempts = 3,
                    DelayStrategy = RetryDelayStrategy.Linear,
                    DelayMilliseconds = 250,
                    DelayIncrementMilliseconds = 250
                }
            }
        }
    ],
    ConfigureAuthentication = true,
    ConfigureAuthorization = true,
    AuthManifest = new AuthManifest
    {
        EnableJwt = true,
        EnableCookies = false,
        EnableCsrfProtection = false,
        Policies =
        [
            new AuthPolicy
            {
                PolicyName = "admin-only",
                RequiredRoles = ["Admin"]
            }
        ]
    },
    UseRateLimit = true,
    RateLimitPolicies =
    [
        new RateLimitPolicy
        {
            PolicyName = "default-fixed-window",
            Algorithm = RateLimitAlgorithm.FixedWindow,
            Scope = RateLimitScope.IpAddress,
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        }
    ],
    IdempotencyPolicy = new IdempotencyPolicy
    {
        Enabled = true,
        RequireHeader = true,
        ExpirationMinutes = 60
    },
    ApiVersioningPolicy = new ApiVersioningPolicy
    {
        Enabled = true,
        Readers = [ApiVersionReaderType.Header],
        HeaderNames = ["x-api-version"],
        DefaultMajorVersion = 1,
        DefaultMinorVersion = 0,
        EnableApiExplorer = true
    },
    SwaggerPolicy = new SwaggerPolicy
    {
        Enabled = true,
        Title = "My API",
        Description = "Service API documentation",
        OpenApiPath = "/swagger/{documentName}/swagger.json",
        SwaggerUiPath = "/swagger",
        Headers =
        [
            new SwaggerHeaderDefinition
            {
                Name = "x-tenant-id",
                Description = "Optional tenant partition key.",
                Required = false,
                AuthorizedOnly = false
            }
        ]
    },
    WebhookSignaturePolicies =
    [
        new WebhookSignaturePolicy
        {
            Name = "stripe",
            PathPrefix = "/webhooks/stripe",
            SecretName = "STRIPE_WEBHOOK_SECRET"
        }
    ],
    HealthCheckPolicy = new HealthCheckPolicy
    {
        Enabled = true,
        OverallEndpointPath = "/health",
        ReadinessEndpointPath = "/health/ready",
        LivenessEndpointPath = "/health/live",
        StartupEndpointPath = "/health/startup",
        IncludeDetailedResponse = true
    }
};

builder.RegisterAbsoluteWebApplicationBuilder(appConfig);

var app = builder.Build();

app.UseAbsolutePipeline(appConfig);

app.Run();

Full Configuration Example

If you prefer to keep configuration in appsettings.json, you can bind your own section into ApplicationConfiguration before calling the registration extension.

Example appsettings.json section:

{
  "AbsoluteCommon": {
    "useRelationalDatabase": true,
    "databasePolicies": [
      {
        "name": "primary",
        "databaseProvider": "PostgreSQL",
        "connectionStringName": "PRIMARY_DB_CONNECTION",
        "initializeDatabase": false,
        "initializeAuditTable": false,
        "maxPoolSize": 100,
        "minPoolSize": 10,
        "commandTimeoutSeconds": 30,
        "resiliencePolicy": {
          "enabled": true,
          "retry": {
            "enabled": true,
            "maxRetryAttempts": 3,
            "delayStrategy": "Exponential",
            "delayMilliseconds": 200,
            "backoffMultiplier": 2.0
          },
          "timeout": {
            "enabled": true,
            "timeoutSeconds": 30
          },
          "circuitBreaker": {
            "enabled": true,
            "handledEventsAllowedBeforeBreaking": 5,
            "durationOfBreakSeconds": 30
          }
        }
      }
    ],
    "useStorage": true,
    "storagePolicies": [
      {
        "name": "files",
        "storageProvider": "S3",
        "connectionStringName": "S3_CONNECTION",
        "bucketName": "my-app-files"
      }
    ],
    "httpClientPolicies": [
      {
        "name": "github",
        "baseAddress": "https://api.github.com/",
        "timeoutSeconds": 30,
        "defaultHeaders": {
          "User-Agent": "my-api"
        },
        "resiliencePolicy": {
          "enabled": true,
          "retry": {
            "enabled": true,
            "maxRetryAttempts": 3,
            "delayStrategy": "Linear",
            "delayMilliseconds": 250,
            "delayIncrementMilliseconds": 250
          }
        }
      }
    ],
    "configureAuthentication": true,
    "configureAuthorization": true,
    "authManifest": {
      "enableJwt": true,
      "enableCookies": false,
      "enableCsrfProtection": false,
      "policies": [
        {
          "policyName": "admin-only",
          "requiredRoles": ["Admin"],
          "requiredClaims": {
            "department": "finance"
          }
        }
      ]
    },
    "useRateLimit": true,
    "rateLimitPolicies": [
      {
        "policyName": "default-fixed-window",
        "algorithm": "FixedWindow",
        "scope": "IpAddress",
        "permitLimit": 100,
        "window": "00:01:00"
      }
    ],
    "idempotencyPolicy": {
      "enabled": true,
      "headerName": "x-idempotency-key",
      "requireHeader": true,
      "replayableMethods": ["POST", "PUT", "PATCH"],
      "expirationMinutes": 60,
      "maximumResponseBodyBytes": 65536,
      "includeQueryStringInKey": false
    },
    "apiVersioningPolicy": {
      "enabled": true,
      "defaultMajorVersion": 1,
      "defaultMinorVersion": 0,
      "assumeDefaultVersionWhenUnspecified": true,
      "reportApiVersions": true,
      "readers": ["Header"],
      "headerNames": ["x-api-version"],
      "enableApiExplorer": true,
      "groupNameFormat": "'v'VVV",
      "substituteApiVersionInUrl": true
    },
    "swaggerPolicy": {
      "enabled": true,
      "title": "My API",
      "description": "Service API documentation",
      "useSwaggerUi": true,
      "openApiPath": "/swagger/{documentName}/swagger.json",
      "swaggerUiPath": "/swagger",
      "persistAuthorization": true,
      "enableTryItOut": true,
      "documentMode": "Single",
      "singleDocumentName": "v1",
      "singleDocumentVersion": "1.0",
      "headers": [
        {
          "name": "x-tenant-id",
          "description": "Optional tenant partition key.",
          "required": false,
          "authorizedOnly": false
        }
      ]
    },
    "webhookSignaturePolicies": [
      {
        "name": "stripe",
        "enabled": true,
        "pathPrefix": "/webhooks/stripe",
        "secretName": "STRIPE_WEBHOOK_SECRET",
        "signatureHeaderName": "x-signature",
        "timestampHeaderName": "x-signature-timestamp",
        "algorithm": "HmacSha256",
        "allowedClockSkewSeconds": 300
      }
    ],
    "healthCheckPolicy": {
      "enabled": true,
      "overallEndpointPath": "/health",
      "livenessEndpointPath": "/health/live",
      "readinessEndpointPath": "/health/ready",
      "startupEndpointPath": "/health/startup",
      "includeDetailedResponse": true
    }
  }
}

Binding example:

using Common.Extensions;
using Common.Models.Configuration;

var builder = WebApplication.CreateBuilder(args);

var appConfig = builder.Configuration
    .GetSection("AbsoluteCommon")
    .Get<ApplicationConfiguration>()
    ?? new ApplicationConfiguration();

builder.RegisterAbsoluteWebApplicationBuilder(appConfig);

var app = builder.Build();

app.UseAbsolutePipeline(appConfig);

app.Run();

Environment Variables and Secrets

This library resolves secrets from environment variables. That is not optional for the features below because the configuration validator checks them up front.

Common variables:

  • JWT_SECRET: required when JWT authentication is enabled, minimum length 32
  • JWT_ISSUER: optional, defaults to AbsoluteAlgorithm.Identity
  • JWT_AUDIENCE: optional, defaults to AbsoluteAlgorithm.Apps
  • database connection string environment variables referenced by each DatabasePolicy.ConnectionStringName
  • storage connection string or credentials environment variables referenced by each StoragePolicy.ConnectionStringName
  • webhook secrets referenced by each WebhookSignaturePolicy.SecretName
  • API key secrets referenced by each AuthorizeKeyAttribute(secretName)

Important rule: ConnectionStringName, SecretName, and similar properties are names of environment variables, not the raw secret values.

What the Pipeline Registers Automatically

RegisterAbsoluteWebApplicationBuilder does all of the following:

  • validates ApplicationConfiguration before registration starts
  • adds environment variables to configuration
  • loads nlog.settings.json
  • configures NLog as the logging provider
  • configures HSTS and lowercase routes
  • adds HttpClient
  • adds IHttpContextAccessor
  • adds request metadata and current-user accessors
  • adds response compression
  • adds controllers
  • installs spreadsheet-formula-safe model binding for strings
  • enables case-insensitive JSON property binding
  • adds ValidateModelFilter
  • suppresses the default ASP.NET Core automatic model-state response so the package can return its own error contract
  • adds response caching
  • conditionally adds API versioning
  • conditionally adds Swagger / OpenAPI generation
  • conditionally adds database repositories
  • conditionally adds storage services
  • conditionally adds named HTTP clients
  • conditionally adds rate limiting
  • conditionally adds authentication and authorization
  • conditionally adds idempotency storage
  • conditionally adds webhook signature validation policies
  • configures forwarded headers
  • adds health checks for self, configured databases, and configured storage providers

Pipeline Order

UseAbsolutePipeline wires middleware and endpoints in this order:

  1. UseForwardedHeaders()
  2. UseHsts() and UseHttpsRedirection() outside development
  3. optional database initialization on startup
  4. UseRouting()
  5. optional UseRateLimiter()
  6. correlation and request metadata middleware
  7. exception middleware
  8. optional webhook signature validation middleware
  9. response compression
  10. response caching
  11. static files
  12. optional OpenAPI / Swagger UI
  13. authentication
  14. optional CSRF protection for cookie-authenticated APIs
  15. authorization
  16. optional idempotency middleware
  17. optional request-scoped database transaction middleware
  18. health endpoints
  19. root GET / operational endpoint
  20. controllers

That order matters. For example:

  • the exception middleware wraps webhook validation, CSRF validation, repository exceptions, and API exceptions
  • idempotency runs before the transaction middleware finishes the request lifecycle
  • request headers are normalized early so correlation and client metadata exist for downstream logic

Configuration Model Reference

ApplicationConfiguration

Top-level root model that enables and configures library features.

Properties:

  • UseRelationalDatabase: enables relational database registration and transaction middleware
  • DatabasePolicies: named database registrations
  • UseStorage: enables object storage registration
  • StoragePolicies: named storage registrations
  • HttpClientPolicies: named HttpClient registrations
  • ApiVersioningPolicy: API versioning options
  • SwaggerPolicy: Swagger / OpenAPI options
  • IdempotencyPolicy: request replay protection options
  • ConfigureAuthentication: enables authentication registration
  • ConfigureAuthorization: enables authorization registration
  • AuthManifest: authentication and authorization options
  • WebhookSignaturePolicies: webhook signature validation options
  • UseRateLimit: enables ASP.NET Core rate limiting registration
  • RateLimitPolicies: named rate limiter policies
  • HealthCheckPolicy: health endpoint paths and payload style

DatabasePolicy

Configures a single named database registration.

Properties:

  • Name: key used to resolve the Repository
  • DatabaseProvider: PostgreSQL or MSSQL
  • ConnectionStringName: environment variable that contains the connection string
  • InitializeDatabase: if true, initialization runs during startup
  • InitializeAuditTable: if true, built-in audit table and audit triggers are created during startup
  • InitializationScript: optional SQL executed during startup initialization
  • MaxPoolSize: provider connection pool max size
  • MinPoolSize: provider connection pool min size
  • CommandTimeoutSeconds: provider timeout setting used for the connection configuration
  • ResiliencePolicy: retry / timeout / circuit breaker settings for repository execution

StoragePolicy

Configures a single named storage registration.

Properties:

  • Name: key used to resolve the StorageService
  • StorageProvider: Minio, AzureBlob, GoogleCloud, or S3
  • ConnectionStringName: environment variable that contains provider credentials or connection string
  • BucketName: target bucket or container name
  • GcpProjectId: required for Google Cloud bucket creation

HttpClientPolicy

Configures one named HttpClient.

Properties:

  • Name: name used with IHttpClientFactory.CreateClient(name)
  • BaseAddress: optional absolute base URL
  • TimeoutSeconds: request timeout for that client
  • DefaultHeaders: header dictionary applied to all requests from that client
  • ResiliencePolicy: retry / timeout / circuit breaker behavior

AuthManifest

Controls auth handlers and authorization policy registration.

Properties:

  • EnableJwt: enables JWT bearer registration
  • EnableCookies: enables cookie authentication registration
  • EnableCsrfProtection: only meaningful when cookies are enabled
  • Policies: named authorization policies

AuthPolicy

Defines one authorization policy.

Properties:

  • PolicyName: policy name used by [Authorize(Policy = "...")]
  • RequiredRoles: role list passed to RequireRole(...)
  • RequiredClaims: claim-value pairs passed to RequireClaim(...)

ApiVersioningPolicy

Configures ASP.NET API versioning.

Properties:

  • Enabled: enables versioning registration
  • DefaultMajorVersion: default major version
  • DefaultMinorVersion: default minor version
  • AssumeDefaultVersionWhenUnspecified: if true, unversioned requests fall back to default
  • ReportApiVersions: emits supported / deprecated version headers
  • Readers: one or more version readers
  • QueryStringParameterName: used for query-string versioning
  • HeaderNames: used for header versioning
  • MediaTypeParameterName: used for media type parameter versioning
  • EnableApiExplorer: enables versioned explorer metadata for Swagger grouping
  • GroupNameFormat: API Explorer version group naming format
  • SubstituteApiVersionInUrl: used with URL-segment versioning

SwaggerPolicy

Configures NSwag document generation and optional Swagger UI exposure.

Properties:

  • Enabled: enables OpenAPI registration and pipeline exposure
  • Title: default document title
  • Description: default document description
  • UseSwaggerUi: if true, the UI is exposed
  • OpenApiPath: JSON route; use {documentName} when multiple documents exist
  • SwaggerUiPath: UI route
  • PersistAuthorization: persist auth values in Swagger UI
  • EnableTryItOut: enable live request execution in the UI
  • DocumentMode: Single or PerApiVersion
  • SingleDocumentName: used when document mode is Single
  • SingleDocumentVersion: used when document mode is Single
  • Documents: per-version document registrations for multi-document mode
  • Headers: extra request headers to render in docs

SwaggerDocumentDefinition

Defines one generated Swagger document.

Properties:

  • DocumentName: internal NSwag document registration name
  • ApiGroupName: API Explorer group to include
  • Version: version label shown in the document
  • Title: optional per-document title override
  • Description: optional per-document description override

SwaggerHeaderDefinition

Defines one request header to surface in generated docs.

Properties:

  • Name: header name
  • Description: human-readable description
  • Required: whether the header is required
  • AuthorizedOnly: if true, the header is only shown on operations that require authorization

IdempotencyPolicy

Configures request replay protection.

Properties:

  • Enabled: enables middleware and in-memory store registration
  • HeaderName: request header used as the idempotency key
  • RequireHeader: if true, missing key becomes a bad request for replayable methods
  • ReplayableMethods: methods that participate in caching and replay
  • ExpirationMinutes: cache lifetime
  • MaximumResponseBodyBytes: maximum response size that can be cached
  • IncludeQueryStringInKey: if true, query string participates in the cache key

WebhookSignaturePolicy

Configures verification for inbound signed webhooks.

Properties:

  • Name: logical policy name
  • Enabled: enables the policy
  • PathPrefix: any request path starting with this prefix is verified
  • SecretName: environment variable containing the shared secret
  • SignatureHeaderName: signature header name
  • TimestampHeaderName: timestamp header name
  • Algorithm: HmacSha256 or HmacSha512
  • AllowedClockSkewSeconds: tolerated request timestamp skew

RateLimitPolicy

Configures one named ASP.NET Core rate limiter policy.

Properties:

  • PolicyName: name referenced by ASP.NET rate limit attributes or endpoint metadata
  • Algorithm: FixedWindow, SlidingWindow, TokenBucket, or Concurrency
  • Scope: Global, IpAddress, User, Endpoint, or ApiKey
  • PermitLimit: used by fixed window, sliding window, and concurrency limiters
  • Window: limiter window or replenishment period
  • SegmentsPerWindow: sliding window segment count
  • TokenLimit: token bucket maximum tokens
  • TokensPerPeriod: token bucket replenishment size

HealthCheckPolicy

Configures health endpoint paths.

Properties:

  • Enabled: exposes health endpoints when true
  • OverallEndpointPath: aggregate endpoint path
  • LivenessEndpointPath: liveness path
  • ReadinessEndpointPath: readiness path
  • StartupEndpointPath: startup path
  • IncludeDetailedResponse: include entry-level detail in responses

ResiliencePolicy

Wraps retry, timeout, and circuit-breaker configuration for both repository and named HttpClient registrations.

Properties:

  • Enabled: master switch
  • Retry: retry settings
  • Timeout: timeout settings
  • CircuitBreaker: circuit breaker settings

RetryResiliencePolicy

Properties:

  • Enabled: enables retries
  • MaxRetryAttempts: maximum number of retries
  • DelayStrategy: Fixed, Linear, Exponential, or CustomSchedule
  • DelayMilliseconds: base delay
  • DelayIncrementMilliseconds: increment used for linear strategy
  • BackoffMultiplier: exponential multiplier
  • DelayScheduleMilliseconds: explicit per-attempt schedule
  • UseExponentialBackoff: legacy compatibility flag; prefer DelayStrategy

TimeoutResiliencePolicy

Properties:

  • Enabled: enables timeouts
  • TimeoutSeconds: timeout duration

CircuitBreakerResiliencePolicy

Properties:

  • Enabled: enables circuit breaker
  • HandledEventsAllowedBeforeBreaking: failure threshold
  • DurationOfBreakSeconds: break period duration

Database and Repository

The package registers one keyed Repository per DatabasePolicy.Name.

The repository is:

  • Dapper-based
  • request-scoped
  • transaction-aware through DatabaseTransactionMiddleware
  • resilient through the configured ResiliencePolicy
  • wired to provider-specific connection creation for PostgreSQL and SQL Server

How request-scoped transactions work

When database support is enabled and the request passes through UseAbsoluteDatabase(), the library opens and manages request-scoped transactions. Repository calls within the same request reuse the same connection and transaction for the same named database.

The repository also sets provider session context values for:

  • current user ID
  • correlation ID

That is useful for built-in auditing and downstream database-side logic.

Resolving a keyed repository

using Common.Database;
using Microsoft.Extensions.DependencyInjection;

public sealed class UsersService
{
    private readonly Repository _repository;

    public UsersService(IServiceProvider serviceProvider)
    {
        _repository = serviceProvider.GetRequiredKeyedService<Repository>("primary");
    }
}

Query methods

The repository exposes these major execution patterns:

  • QueryInterpolatedAsync<T>(FormattableString sql, ...)
  • QueryAsync<T>(string sql, object? parameters = null, ...)
  • QueryAysnc<T>(...) as a compatibility shim for the legacy typo
  • ExecuteInterpolatedAsync(FormattableString sql, ...)
  • ExecuteAsync(string sql, object? parameters = null, ...)
  • ExecuteStoredProcedureAsync(string procedureName, object? parameters = null, ...)
  • ExecuteScalarAsync<T>(string sql, object? parameters = null, ...)
  • QueryPageAsync<T>(RepositoryPagedQuery query, PagedRequest? request = null, ...)
  • ExecuteOptimisticUpdateAsync(RepositoryOptimisticUpdateDefinition definition, ...)

Preferred SQL usage

Use interpolated overloads for values whenever possible.

var users = await _repository.QueryInterpolatedAsync<UserRow>(
    $"SELECT id, email FROM users WHERE tenant_id = {tenantId} AND is_active = {true}",
    cancellationToken: cancellationToken);

The interpolated values are converted into parameters internally. That avoids unsafe value concatenation.

Use raw SQL overloads only when you genuinely need full control over the SQL text and parameter object.

var users = await _repository.QueryAsync<UserRow>(
    "SELECT id, email FROM users WHERE tenant_id = @tenantId",
    new { tenantId },
    cancellationToken: cancellationToken);

Important limitation: identifiers such as table names, schema names, or column names cannot be parameterized. Do not accept those from untrusted input.

Stored procedures

await _repository.ExecuteStoredProcedureAsync(
    "sp_refresh_user_projection",
    new { userId },
    cancellationToken);

Scalar queries

var count = await _repository.ExecuteScalarAsync<long>(
    "SELECT COUNT(1) FROM users WHERE tenant_id = @tenantId",
    new { tenantId },
    cancellationToken: cancellationToken);

Startup database initialization

If InitializeDatabase is enabled for a policy, the startup pipeline runs initialization SQL. If InitializeAuditTable is also enabled, the package creates a built-in audit_logs table plus provider-specific triggers.

Use that feature only when your deployment model permits schema bootstrapping from application startup.

Pagination, Filtering, Sorting, and Paged Queries

The library includes a safe paged-query abstraction for repository access.

Core models:

  • PagedRequest
  • PagedResult<T>
  • SortDescriptor
  • FilterDescriptor
  • RepositoryPagedQuery

PagedRequest

Properties:

  • PageNumber: 1-based page number, default 1
  • PageSize: page size, default 25
  • SearchTerm: optional free-text search term
  • Sorts: list of SortDescriptor
  • Filters: list of FilterDescriptor
  • Offset: computed zero-based row offset

SortDescriptor

Properties:

  • Field: logical field name requested by the client
  • Direction: Ascending or Descending

FilterDescriptor

Properties:

  • Field: logical field name
  • Operator: Equals, NotEquals, Contains, StartsWith, EndsWith, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, In, Between
  • Value: primary value for unary filters
  • Values: additional values for In and Between

RepositoryPagedQuery

This model is how you whitelist search, filter, and sort behavior.

Properties:

  • SelectSql: trusted base SELECT SQL, without final paging clause
  • CountSql: trusted base count SQL matching the same source rows
  • DefaultOrderBy: default sort expression or ORDER BY clause
  • SortColumns: map from client-facing sort field names to trusted SQL expressions
  • FilterColumns: map from client-facing filter field names to trusted SQL expressions
  • SearchColumns: trusted SQL expressions that participate in text search

Paged query example

using Common.Models.Database;
using Common.Models.Pagination;

var query = new RepositoryPagedQuery
{
    SelectSql = @"
        SELECT u.id, u.email, u.display_name AS displayName, u.updated_at AS updatedAt
        FROM users u",
    CountSql = @"
        SELECT COUNT(1)
        FROM users u",
    DefaultOrderBy = "u.updated_at DESC",
    SortColumns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        ["email"] = "u.email",
        ["displayName"] = "u.display_name",
        ["updatedAt"] = "u.updated_at"
    },
    FilterColumns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        ["email"] = "u.email",
        ["status"] = "u.status",
        ["createdAt"] = "u.created_at"
    },
    SearchColumns = ["u.email", "u.display_name"]
};

var request = new PagedRequest
{
    PageNumber = 1,
    PageSize = 20,
    SearchTerm = "lalith",
    Sorts =
    [
        new SortDescriptor { Field = "updatedAt", Direction = SortDirection.Descending }
    ],
    Filters =
    [
        new FilterDescriptor
        {
            Field = "status",
            Operator = FilterOperator.Equals,
            Value = "active"
        }
    ]
};

var result = await _repository.QueryPageAsync<UserListItem>(query, request, cancellationToken: cancellationToken);

The critical design point is that callers only send logical field names. The SQL expressions remain server-owned through the SortColumns and FilterColumns maps.

Optimistic Concurrency and ETags

The library gives you both utility helpers and repository-level optimistic update support.

Core types:

  • RepositoryOptimisticUpdateDefinition
  • RepositoryOptimisticUpdateResult
  • ETagUtility
  • OptimisticConcurrencyUtility

RepositoryOptimisticUpdateDefinition

Properties:

  • ResourceName: used in generated error messages
  • UpdateSql: update SQL; should include expected version matching in the WHERE clause
  • ExistsSql: optional existence check SQL
  • CurrentVersionSql: optional SQL used to read the current version token
  • RequireIfMatchHeader: if true, request must provide a matching If-Match header

Repository optimistic update example

var definition = new RepositoryOptimisticUpdateDefinition
{
    ResourceName = "user",
    CurrentVersionSql = "SELECT version_token FROM users WHERE id = @id",
    ExistsSql = "SELECT CASE WHEN EXISTS (SELECT 1 FROM users WHERE id = @id) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END",
    UpdateSql = @"
        UPDATE users
        SET display_name = @displayName,
            version_token = @newVersionToken
        WHERE id = @id
          AND version_token = @expectedVersionToken",
    RequireIfMatchHeader = true
};

var result = await _repository.ExecuteOptimisticUpdateAsync(
    definition,
    new
    {
        id,
        displayName,
        expectedVersionToken,
        newVersionToken
    },
    cancellationToken: cancellationToken);

Successful updates return:

  • RowsAffected
  • CurrentVersionToken
  • CurrentEtag

If the resource no longer exists, the library throws E404. If the row exists but a concurrent update happened, the library throws E409. If RequireIfMatchHeader is enabled and the header is missing or stale, it throws E412.

Manual ETag usage in a controller

using Common.Utilities;
using Microsoft.AspNetCore.Mvc;

[HttpGet("{id:int}")]
public IActionResult GetUser(int id)
{
    var dto = new { Id = id, Name = "Lalith", Version = "42" };
    var etag = OptimisticConcurrencyUtility.CreateETag(dto.Version);

    if (OptimisticConcurrencyUtility.ShouldReturnNotModified(Request, etag))
    {
        return StatusCode(StatusCodes.Status304NotModified);
    }

    ETagUtility.Apply(Response, etag);
    return Ok(dto);
}

Utility capabilities in this area

ETagUtility provides:

  • strong ETag creation from strings, bytes, and objects
  • weak ETag creation
  • request header extraction for If-Match and If-None-Match
  • exact or weak comparison matching
  • normalization helpers

OptimisticConcurrencyUtility provides:

  • version token creation from row-version bytes, long, or UTC timestamps
  • ETag creation from version tokens
  • explicit token equality checks
  • request precondition validation through If-Match
  • 304 Not Modified decision support via If-None-Match

Storage Services

The package registers one keyed StorageService per StoragePolicy.Name.

Supported providers:

  • MinIO
  • Azure Blob Storage
  • Google Cloud Storage
  • Amazon S3

StorageService public methods:

  • UploadAsync(FileContent fileContent)
  • GetDownloadUrlAsync(string fileName, TimeSpan expiry)

What upload does

When you upload content through StorageService:

  • the original filename is sanitized
  • the extension is preserved in a safe way
  • a new provider-safe object name is generated
  • the object is uploaded to the configured bucket / container

FileContent

FileContent represents one upload request.

Properties:

  • FileName: original file name
  • Base64Content: optional base64 payload
  • ByteArrayContent: optional raw bytes
  • ContentType: MIME type, default application/octet-stream

Method:

  • GetContentStream(): returns a readable stream from the configured content

Storage resolution example

using Common.Models.Storage;
using Common.Services;
using Microsoft.Extensions.DependencyInjection;

public sealed class AvatarService
{
    private readonly StorageService _storage;

    public AvatarService(IServiceProvider serviceProvider)
    {
        _storage = serviceProvider.GetRequiredKeyedService<StorageService>("files");
    }

    public async Task<string> UploadAsync(byte[] bytes, string fileName)
    {
        return await _storage.UploadAsync(new FileContent
        {
            FileName = fileName,
            ByteArrayContent = bytes,
            ContentType = "image/png"
        });
    }
}

Download URL example

var url = await _storage.GetDownloadUrlAsync(fileName, TimeSpan.FromMinutes(15));

HTTP Clients and Resilience

Named clients are registered from HttpClientPolicy objects.

Resolve them with IHttpClientFactory:

public sealed class GitHubClient
{
    private readonly HttpClient _httpClient;

    public GitHubClient(IHttpClientFactory factory)
    {
        _httpClient = factory.CreateClient("github");
    }

    public async Task<string> GetRateLimitAsync(CancellationToken cancellationToken)
    {
        return await _httpClient.GetStringAsync("rate_limit", cancellationToken);
    }
}

If resilience is enabled for the client, the package adds a Polly-backed message handler.

HTTP resilience behavior

The HTTP resilience factory retries and/or breaks the circuit for:

  • HttpRequestException
  • timeouts
  • status codes 408, 429, and 5xx

Timeouts and backoff strategy are driven by the supplied ResiliencePolicy.

Database resilience behavior

Repository execution uses DBResiliencePolicyFactory and handles transient database faults such as:

  • general DbException
  • timeout failures
  • PostgreSQL transient exceptions
  • SQL Server deadlock style failures

Authentication and Authorization

The package supports three auth shapes:

  • JWT only
  • cookie only
  • hybrid JWT or cookie via policy scheme

Registration behavior

When both JWT and cookies are enabled, the package registers a policy scheme named AbsoluteHybrid and chooses the real scheme per request:

  • if Authorization: Bearer ... is present, JWT is used
  • otherwise cookie auth is used

JWT requirements

Required environment variable:

  • JWT_SECRET with at least 32 characters

Optional environment variables:

  • JWT_ISSUER
  • JWT_AUDIENCE

Defaults:

  • issuer: AbsoluteAlgorithm.Identity
  • audience: AbsoluteAlgorithm.Apps

The package registers a cookie named AbsoluteAuth with:

  • HttpOnly = true
  • SameSite = Strict
  • SecurePolicy = Always
  • seven-day expiry
  • sliding expiration

Redirect-to-login is converted into HTTP 401 instead of browser redirection, which is correct for APIs.

Authorization policies

Policies defined in AuthManifest.Policies are added to ASP.NET authorization.

Example:

[Authorize(Policy = "admin-only")]
[HttpGet("admin/report")]
public IActionResult GetAdminReport()
{
    return Ok();
}

AuthorizeKey Attribute

AuthorizeKeyAttribute is a dedicated API-key authorization attribute.

Constructor:

new AuthorizeKeyAttribute(secretName, headerName = HEADER.APIKEY)

Behavior:

  • reads the expected API key from an environment variable
  • reads the provided key from the request header
  • compares values using constant-time comparison
  • returns HTTP 401 with the standard error payload when invalid
  • respects [AllowAnonymous]

Example

using Common.Filters;

[ApiController]
[Route("api/integrations")]
public class IntegrationsController : ControllerBase
{
    [AuthorizeKey("INTERNAL_API_KEY")]
    [HttpPost("sync")]
    public IActionResult Sync()
    {
        return Ok();
    }
}

Custom header example:

[AuthorizeKey("PARTNER_API_KEY", "x-partner-key")]

CSRF Protection

CSRF protection is deliberately tied to cookie authentication.

It is only active when:

  • AuthManifest.EnableCookies == true
  • AuthManifest.EnableCsrfProtection == true

Behavior:

  • on safe methods (GET, HEAD, OPTIONS, TRACE), the middleware generates antiforgery tokens and writes a readable XSRF-TOKEN cookie
  • on mutation methods, the middleware validates the request token for cookie-authenticated requests that are not using bearer tokens
  • failures become E403 with a standardized error response

Antiforgery registration uses:

  • request header: x-csrf-token
  • antiforgery cookie: __Host-AbsoluteCsrf

This library is making the correct distinction here: bearer token APIs do not need CSRF defense because the browser does not automatically attach bearer tokens the way it does cookies.

Rate Limiting

The package wraps ASP.NET Core rate limiting and lets you define named policies through configuration.

Supported algorithms:

  • FixedWindow
  • SlidingWindow
  • TokenBucket
  • Concurrency

Supported scopes:

  • Global
  • IpAddress
  • User
  • Endpoint
  • ApiKey

Rejected requests return a standardized 429 response with:

  • errorCode = E429
  • errorMessage = "Rate limit exceeded. Please try again later."

Example policy

new RateLimitPolicy
{
    PolicyName = "login-limit",
    Algorithm = RateLimitAlgorithm.FixedWindow,
    Scope = RateLimitScope.IpAddress,
    PermitLimit = 5,
    Window = TimeSpan.FromMinutes(1)
}

You can then apply the ASP.NET Core rate-limiting attribute in your API project using the registered policy name.

Idempotency

The idempotency middleware protects write endpoints from duplicate processing.

Behavior summary:

  • only configured methods are eligible, default POST, PUT, PATCH
  • if the key header is required and missing, request fails with E400
  • if a matching request is already in progress, request fails with E409
  • if a completed cached response exists, it is replayed
  • replayed responses include header x-idempotency-replayed: true
  • only successful 2xx responses are cached
  • responses larger than MaximumResponseBodyBytes are not cached

The default store implementation is InMemoryIdempotencyStore.

Default key composition

The cache key includes:

  • HTTP method
  • request path, and optionally query string
  • current subject (UserId, otherwise client IP, otherwise anonymous)
  • idempotency key header value

Example

Client sends:

POST /api/orders
x-idempotency-key: ord_20260319_001

If the exact same request arrives again after a successful response, the cached response is replayed instead of executing the operation again.

Replacing the store

The abstraction is IIdempotencyStore.

Members:

  • Get(string key)
  • TryBegin(string key)
  • Complete(string key, IdempotencyStoredResponse response, TimeSpan expiration)
  • Abandon(string key)

If you need distributed replay support, replace the default in-memory store with your own implementation.

Webhook Signature Validation

The webhook middleware validates inbound requests for configured path prefixes.

It performs these checks:

  • request path matches a configured enabled policy
  • required signature and timestamp headers are present
  • shared secret is available from the configured environment variable
  • request body is buffered and read safely
  • signature and timestamp are verified using RequestSignatureUtility

Failures return E401.

Example webhook policy

new WebhookSignaturePolicy
{
    Name = "stripe",
    Enabled = true,
    PathPrefix = "/webhooks/stripe",
    SecretName = "STRIPE_WEBHOOK_SECRET",
    SignatureHeaderName = "stripe-signature",
    TimestampHeaderName = "x-signature-timestamp",
    Algorithm = RequestSignatureAlgorithm.HmacSha256,
    AllowedClockSkewSeconds = 300
}

Utility support

RequestSignatureUtility exposes:

  • GenerateTimestamp(...)
  • ComputeSignature(...)
  • VerifySignature(...)

That lets you verify signatures manually in tests or external integration code if needed.

API Versioning

Versioning is configured only through ApiVersioningPolicy.

Supported reader types:

  • QueryString
  • Header
  • MediaType
  • UrlSegment

Readers can be combined. The builder uses ApiVersionReader.Combine(...) when more than one reader is configured.

Header versioning example

new ApiVersioningPolicy
{
    Enabled = true,
    Readers = [ApiVersionReaderType.Header],
    HeaderNames = ["x-api-version"],
    DefaultMajorVersion = 1,
    DefaultMinorVersion = 0,
    AssumeDefaultVersionWhenUnspecified = true,
    ReportApiVersions = true,
    EnableApiExplorer = true,
    GroupNameFormat = "'v'VVV",
    SubstituteApiVersionInUrl = true
}

Query-string versioning example

new ApiVersioningPolicy
{
    Enabled = true,
    Readers = [ApiVersionReaderType.QueryString],
    QueryStringParameterName = "api-version"
}

Multi-reader example

new ApiVersioningPolicy
{
    Enabled = true,
    Readers = [ApiVersionReaderType.Header, ApiVersionReaderType.QueryString],
    HeaderNames = ["x-api-version"],
    QueryStringParameterName = "api-version"
}

Swagger and OpenAPI with NSwag

The package uses NSwag, not Swashbuckle.

Key behaviors:

  • OpenAPI is only registered when SwaggerPolicy.Enabled == true
  • supports a single document or one document per API version group
  • supports Swagger UI exposure or JSON-only exposure
  • adds bearer security when JWT auth is enabled
  • adds cookie security when cookie auth is enabled
  • adds API key security only for endpoints decorated with AuthorizeKeyAttribute
  • shows request header requirements defined through SwaggerHeaderDefinition
  • automatically adds API version header documentation when header versioning is enabled and the header is not already defined in SwaggerPolicy.Headers

Single-document mode

new SwaggerPolicy
{
    Enabled = true,
    Title = "My API",
    Description = "Main API documentation",
    DocumentMode = SwaggerDocumentMode.Single,
    SingleDocumentName = "v1",
    SingleDocumentVersion = "1.0",
    OpenApiPath = "/swagger/{documentName}/swagger.json",
    SwaggerUiPath = "/swagger"
}

Per-version document mode

new SwaggerPolicy
{
    Enabled = true,
    Title = "My API",
    DocumentMode = SwaggerDocumentMode.PerApiVersion,
    Documents =
    [
        new SwaggerDocumentDefinition
        {
            DocumentName = "v1",
            ApiGroupName = "v1",
            Version = "1.0",
            Title = "My API v1"
        },
        new SwaggerDocumentDefinition
        {
            DocumentName = "v2",
            ApiGroupName = "v2",
            Version = "2.0",
            Title = "My API v2"
        }
    ]
}

Documenting required headers

new SwaggerPolicy
{
    Enabled = true,
    Headers =
    [
        new SwaggerHeaderDefinition
        {
            Name = "x-tenant-id",
            Description = "Tenant partition key.",
            Required = true,
            AuthorizedOnly = true
        },
        new SwaggerHeaderDefinition
        {
            Name = "x-correlation-id",
            Description = "Optional caller-supplied correlation ID.",
            Required = false,
            AuthorizedOnly = false
        }
    ]
}

Lock symbol behavior

The package includes custom NSwag operation processors that inspect endpoint metadata.

The documentation layer marks operations as protected only when the action or controller actually requires authorization through:

  • [Authorize]
  • [AuthorizeKey(...)]

That is exactly what you want. The docs do not pretend every operation is locked when only a subset is protected.

Health Checks

Health checks are enabled unless you explicitly disable them via HealthCheckPolicy.Enabled = false.

Default endpoints:

  • /health
  • /health/live
  • /health/ready
  • /health/startup

What is registered

  • self check tagged as live, ready, and startup
  • PostgreSQL checks for configured PostgreSQL policies
  • SQL Server checks for configured SQL Server policies
  • MinIO checks for MinIO storage policies
  • Azure Blob checks for Azure storage policies
  • S3 checks for S3 storage policies
  • GCP storage checks via GcpStorageHealthCheck

Response styles

When IncludeDetailedResponse is true, overall and readiness endpoints return:

  • aggregate status
  • total duration
  • per-check entry status
  • tags
  • descriptions
  • exception messages when present

When IncludeDetailedResponse is false, the health payload is minimal.

Filters

ValidateModelFilter

This filter is applied globally by the builder.

What it does:

  • intercepts invalid model state before controller actions run
  • returns the package response contract instead of the default ASP.NET Core validation payload
  • preserves malformed JSON and invalid body messages instead of collapsing everything into a generic message
  • returns E400 for malformed body / JSON problems
  • returns E422 for semantic validation failures

Validation response shape:

{
  "isSuccess": false,
  "error": {
    "errorCode": "E422",
    "errorMessage": "The Email field is required.",
    "validationErrors": [
      {
        "field": "email",
        "messages": ["The Email field is required."]
      }
    ]
  }
}

AuthorizeKeyAttribute

Covered earlier in the auth section, but it is also part of the filter surface. It implements IAuthorizationFilter and can be applied at controller or action level.

Middlewares

RequestHeaderMiddleware

Responsibilities:

  • ensures x-correlation-id exists
  • normalizes and writes x-ip-address
  • normalizes and writes x-user-agent
  • writes the same metadata to both request and response headers

ExceptionMiddleware

Responsibilities:

  • converts ApiException to standardized error responses
  • maps provider-specific database exceptions to safer public messages
  • converts generic failures to E500
  • converts cancellation to E499

DatabaseTransactionMiddleware

Responsibilities:

  • maintains request-scoped transactions for enabled databases
  • ensures repository operations share the same transaction per named database in the current request
  • commits or rolls back at the end of request processing based on request outcome

IdempotencyMiddleware

Responsibilities:

  • detects duplicate write requests
  • reserves in-flight idempotency keys
  • replays cached successful responses
  • marks replayed responses with x-idempotency-replayed

CsrfMiddleware

Responsibilities:

  • issues antiforgery tokens on safe methods
  • validates request tokens for cookie-authenticated mutation requests

WebhookSignatureMiddleware

Responsibilities:

  • matches request paths against configured webhook policies
  • validates signature and timestamp headers
  • verifies request payload integrity before controller code runs

Exceptions and Error Contracts

ApiException

ApiException is the core exception type for expected API errors. It carries:

  • ErrorCode
  • ErrorMessage

Throw it directly or use the factory helpers in ApiExceptions.

ApiExceptions helper

Factory members:

  • ApiExceptions.Unauthorized
  • ApiExceptions.Forbidden
  • ApiExceptions.Notfound(entity)
  • ApiExceptions.Badrequest(message)
  • ApiExceptions.PreconditionFailed(message)
  • ApiExceptions.Conflict(message)
  • ApiExceptions.FromCode(code, message)

ApiResponse<T>

Standard response envelope:

  • IsSuccess
  • Data
  • Error

ErrorResponse

Error payload fields:

  • ErrorCode
  • ErrorMessage
  • ValidationErrors

ValidationErrorDetail

Fields:

  • Field
  • Messages

Error code catalog

  • E400: bad request
  • E401: unauthorized
  • E403: forbidden
  • E404: not found
  • E409: conflict
  • E410: gone
  • E412: precondition failed
  • E422: unprocessable entity
  • E429: too many requests
  • E499: operation cancelled
  • E500: internal server error

Sanitizers

The library includes focused sanitization features rather than broad, destructive input mutation.

SpreadsheetFormulaSanitizer

Purpose:

  • mitigates CSV / spreadsheet formula injection

Behavior:

  • if a string starts with dangerous spreadsheet-leading characters such as =, +, -, @, tab, carriage return, or line feed, the sanitizer prefixes a single quote

Where it is applied:

  • incoming string JSON values through a custom JSON converter
  • incoming string model-bound values through a custom model binder provider
  • CSV-related workflows where you want safe spreadsheet exports

SpreadsheetFormulaSanitizingStringJsonConverter

Applies spreadsheet formula sanitization while deserializing JSON string values.

SpreadsheetFormulaSanitizingStringModelBinderProvider

Registers a string model binder that sanitizes incoming string values.

SpreadsheetFormulaSanitizingStringModelBinder

The binder implementation used by the provider.

FileNameSanitizer

Purpose:

  • strips unsafe path and filename content before storage uploads

Used by:

  • StorageService

Services and Accessors

ICurrentUserAccessor

Provides request-scoped user data:

  • Principal
  • UserId
  • Email
  • IsAuthenticated
  • Roles

Default implementation:

  • HttpContextCurrentUserAccessor

IRequestMetadataAccessor

Provides current normalized request metadata through:

  • Current

Default implementation:

  • HttpContextRequestMetadataAccessor

RequestMetadata

Fields:

  • CorrelationId
  • UserId
  • TenantId
  • IdempotencyKey
  • ClientIpAddress
  • UserAgent
  • Path
  • Method
  • IsAuthenticated

IIdempotencyStore

Interface used by the idempotency middleware.

Default implementation:

  • InMemoryIdempotencyStore

Support models:

  • IdempotencyLookupResult
  • IdempotencyStoredResponse

Models

This section is the condensed model catalog for the library.

Auth Models

  • AuthManifest: enables JWT, cookies, CSRF, and holds authorization policy definitions
  • AuthPolicy: named authorization policy made of roles and claim requirements

Configuration Models

  • ApplicationConfiguration: top-level feature switchboard
  • ApiVersioningPolicy: API versioning behavior

Database Models

  • DatabasePolicy: database registration options
  • RepositoryPagedQuery: trusted paged query descriptor
  • RepositoryOptimisticUpdateDefinition: optimistic update descriptor
  • RepositoryOptimisticUpdateResult: optimistic update result

Documentation Models

  • SwaggerPolicy: NSwag and Swagger UI configuration
  • SwaggerDocumentDefinition: one generated document definition
  • SwaggerHeaderDefinition: one documented request header definition

Health Models

  • HealthCheckPolicy: health endpoint configuration

HTTP Models

  • HttpClientPolicy: named client registration and resilience configuration

Idempotency Models

  • IdempotencyPolicy: idempotency middleware settings

Pagination Models

  • PagedRequest
  • PagedResult<T>
  • SortDescriptor
  • FilterDescriptor

Rate Limit Models

  • RateLimitPolicy

Request Models

  • RequestMetadata

Response Models

  • ApiResponse<T>
  • ErrorResponse
  • ValidationErrorDetail

Resilience Models

  • ResiliencePolicy
  • RetryResiliencePolicy
  • TimeoutResiliencePolicy
  • CircuitBreakerResiliencePolicy

Storage Models

  • StoragePolicy
  • FileContent

Webhook Models

  • WebhookSignaturePolicy

Utilities

The utility surface is intentionally broad. These classes are standalone helpers you can use even outside the main pipeline features.

AuthUtility

Use for building principals, cookie sign-in/sign-out, and secure cookie helpers.

Key methods:

  • CreateClaim(...)
  • CreateIdentity(...)
  • CreatePrincipal(...)
  • SignInWithCookieAsync(...)
  • SignOutCookieAsync(...)
  • AppendSecureCookie(...)
  • AppendRefreshTokenCookie(...)
  • DeleteCookie(...)

ClaimUtility

Use for reading common identity data from a ClaimsPrincipal.

Key methods:

  • GetClaimValue(...)
  • GetUserId(...)
  • GetEmail(...)
  • GetRoles(...)
  • HasRole(...)
  • HasAnyRole(...)

TokenUtility

Use for token creation, JWT issuance and validation, key derivation, and constant-time comparison.

Key methods:

  • GenerateToken(...)
  • GenerateRefreshToken(...)
  • GenerateOneTimeToken(...)
  • GenerateApiKey(...)
  • GenerateSymmetricKey(...)
  • JWT GenerateToken(...) overloads
  • ValidateToken(...)
  • CreateSecurityKey(...)
  • GenerateNumericCode(...)
  • FixedTimeEquals(...)
  • HashToken(...)
  • VerifyHashedToken(...)

HashingUtility

Use for SHA-256 and SHA-512 hashing from strings, byte arrays, streams, or base64 payloads.

Key methods:

  • ComputeHash(...)
  • ComputeHashFromBase64(...)
  • ComputeSha256(...)
  • ComputeSha512(...)

PasswordUtility

Use for password hashing and verification.

Key methods:

  • HashPassword(...)
  • VerifyPassword(...)

EncryptionUtility

Use for symmetric encryption and decryption.

Key methods:

  • GenerateKey(...)
  • Encrypt(string, key, ...)
  • Decrypt(string, key, ...)
  • Encrypt(byte[], key)
  • Decrypt(byte[], key)

AsymmetricKeyUtility

Use for RSA / ECDsa keypair generation and digital signatures.

Key methods:

  • GeneratePrivateKey(...)
  • GetPublicKey(...)
  • GenerateKeyPair(...)
  • SignData(...)
  • VerifySignature(...)

Associated record:

  • AsymmetricKeyPair

RequestSignatureUtility

Use for HMAC-style request signing and verification.

Key methods:

  • GenerateTimestamp(...)
  • ComputeSignature(...)
  • VerifySignature(...)

ETagUtility

Use for HTTP entity tags.

Key methods:

  • CreateStrong(...)
  • CreateWeak(...)
  • Apply(HttpResponse, etag)
  • GetIfMatch(...)
  • GetIfNoneMatch(...)
  • IsWildcard(...)
  • Matches(...)
  • AnyMatch(...)
  • Normalize(...)
  • ToWeak(...)

OptimisticConcurrencyUtility

Use for version tokens and If-Match / If-None-Match semantics.

Key methods:

  • CreateVersionToken(byte[])
  • CreateVersionToken(long)
  • CreateVersionToken(DateTime)
  • CreateETag(...)
  • Matches(...)
  • EnsureMatches(...)
  • RequireIfMatch(...)
  • EnsureIfMatch(...)
  • ShouldReturnNotModified(...)

CsvUtility

Use for safe CSV import / export.

Key methods:

  • ReadFromCsv<T>(string csvContent, ...)
  • ReadFromCsv<T>(Stream stream, ...)
  • WriteToCsv<T>(IEnumerable<T> records, ...)
  • WriteToCsv<T>(IEnumerable<T> records, Stream stream, ...)

DateTimeUtility

Use for UTC normalization and Unix time conversions.

Key methods:

  • EnsureUtc(...)
  • StartOfDayUtc(...)
  • EndOfDayUtc(...)
  • ToUnixTimeSeconds(...)
  • FromUnixTimeSeconds(...)

FileUtility

Use for file name and stream helpers.

Key methods:

  • GetExtension(...)
  • GetFileNameWithoutExtension(...)
  • GetContentType(...)
  • FormatSize(...)
  • ReadAllBytes(...)
  • ReadAllText(...)

HttpUtility

Use for request helpers and query-string handling.

Key methods:

  • GetHeaderValue(...)
  • GetBearerToken(...)
  • GetClientIpAddress(...)
  • GetCorrelationId(...)
  • GetTenantId(...)
  • GetIdempotencyKey(...)
  • GetRequestMetadata(...)
  • BuildQueryString(...)
  • AppendQueryString(...)

JsonUtility

Use for consistent serialization and deserialization.

Key methods:

  • CreateDefaultOptions()
  • Serialize<T>(...)
  • Deserialize<T>(...)
  • Deserialize(string json, Type type, ...)

ReflectionUtility

Use for property discovery helpers.

Key methods:

  • GetPublicInstanceProperties(Type)
  • GetReadableProperties(Type)
  • GetWritableProperties(Type)
  • generic overloads for the same operations

EnumUtility

Use for safe enum parsing and discovery.

Key methods:

  • Parse<TEnum>(...)
  • TryParse<TEnum>(...)
  • GetNames<TEnum>()
  • GetValues<TEnum>()
  • IsDefined<TEnum>(...)

CompressionUtility

Use for GZip and Brotli compression.

Key methods:

  • CompressGzip(...)
  • DecompressGzip(...)
  • DecompressGzipToString(...)
  • CompressBrotli(...)
  • DecompressBrotli(...)
  • DecompressBrotliToString(...)

Constraints and Enums

Header constants

HEADER defines the built-in header names used by the package:

  • x-correlation-id
  • x-user-agent
  • x-ip-address
  • User-Agent
  • x-tenant-id
  • x-idempotency-key
  • x-api-key
  • x-idempotency-replayed
  • x-csrf-token
  • x-signature
  • x-signature-timestamp
  • ETag
  • If-Match
  • If-None-Match

Database constraint key

DATABASE.CONNECTIONSKEY is the HttpContext.Items key used for request-scoped database connections and transactions.

Core enums

DatabaseProvider

  • PostgreSQL
  • MSSQL

StorageProvider

  • Minio
  • AzureBlob
  • GoogleCloud
  • S3

RateLimitAlgorithm

  • FixedWindow
  • SlidingWindow
  • TokenBucket
  • Concurrency

RateLimitScope

  • Global
  • IpAddress
  • User
  • Endpoint
  • ApiKey

ApiVersionReaderType

  • QueryString
  • Header
  • MediaType
  • UrlSegment

SwaggerDocumentMode

  • Single
  • PerApiVersion

SortDirection

  • Ascending
  • Descending

FilterOperator

  • Equals
  • NotEquals
  • Contains
  • StartsWith
  • EndsWith
  • GreaterThan
  • GreaterThanOrEqual
  • LessThan
  • LessThanOrEqual
  • In
  • Between

RequestSignatureAlgorithm

  • HmacSha256
  • HmacSha512

RetryDelayStrategy

  • Fixed
  • Linear
  • Exponential
  • CustomSchedule

ResilienceType

  • Retry
  • Timeout
  • CircuitBreaker

HashAlgorithmType

  • hash algorithm choices used by HashingUtility

AsymmetricKeyAlgorithmType

  • asymmetric key algorithm choices used by AsymmetricKeyUtility

End-to-End Integration Pattern

If you want a simple mental model for this package, use this pattern:

  1. Put infrastructure settings into ApplicationConfiguration.
  2. Put secrets into environment variables.
  3. Register the package once in Program.cs.
  4. Resolve keyed Repository and keyed StorageService instances where needed.
  5. Resolve named HttpClient instances with IHttpClientFactory.
  6. Use [Authorize], [Authorize(Policy = "...")], and [AuthorizeKey(...)] on endpoints.
  7. Return your data inside normal ASP.NET action results; the package handles errors, validation, docs, request metadata, and optional infrastructure features.

Operational Notes and Recommendations

  • Prefer QueryInterpolatedAsync and ExecuteInterpolatedAsync for value-based SQL.
  • Only use raw SQL overloads when you genuinely need direct SQL control.
  • Keep SQL identifiers server-owned. Never let users choose raw table or column names.
  • Enable CSRF only for cookie-authenticated APIs.
  • Use AuthorizeKeyAttribute for machine-to-machine endpoints that rely on API keys.
  • Use SwaggerPolicy.Headers to explicitly document required custom headers for consumers.
  • If you want versioned Swagger documents, enable API versioning and set SwaggerPolicy.DocumentMode = PerApiVersion.
  • If you need distributed idempotency, replace InMemoryIdempotencyStore with a centralized implementation.
  • If startup schema creation is too risky for your environment, leave InitializeDatabase disabled and manage schema migrations separately.
  • Keep JWT_SECRET strong and rotated through your secret-management process.
  • Health checks are useful only when the backing resources are truly representative of application readiness. Configure them deliberately.

Final Summary

This package is not a single-purpose helper library. It is a reusable API infrastructure layer. The intended usage is to install it once, configure it centrally, and then let application projects consume:

  • a standard pipeline
  • keyed infrastructure services
  • safe defaults for request handling
  • consistent response and error contracts
  • built-in security and documentation primitives
  • reusable utility functions for common backend concerns

If your API project needs databases, object storage, auth, API key protection, Swagger, versioning, idempotency, webhooks, health checks, resilience, and standardized request/response behavior without re-implementing those concerns each time, this package is the integration point.

Product 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. 
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