AbsoluteAlgorithm.Api.Common
1.0.0-dev.23
dotnet add package AbsoluteAlgorithm.Api.Common --version 1.0.0-dev.23
NuGet\Install-Package AbsoluteAlgorithm.Api.Common -Version 1.0.0-dev.23
<PackageReference Include="AbsoluteAlgorithm.Api.Common" Version="1.0.0-dev.23" />
<PackageVersion Include="AbsoluteAlgorithm.Api.Common" Version="1.0.0-dev.23" />
<PackageReference Include="AbsoluteAlgorithm.Api.Common" />
paket add AbsoluteAlgorithm.Api.Common --version 1.0.0-dev.23
#r "nuget: AbsoluteAlgorithm.Api.Common, 1.0.0-dev.23"
#:package AbsoluteAlgorithm.Api.Common@1.0.0-dev.23
#addin nuget:?package=AbsoluteAlgorithm.Api.Common&version=1.0.0-dev.23&prerelease
#tool nuget:?package=AbsoluteAlgorithm.Api.Common&version=1.0.0-dev.23&prerelease
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
HttpClientregistrations 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
- NuGet Installation
- Supported Runtime and Dependencies
- How to Wire the Library
- Minimal Program.cs Example
- Full Configuration Example
- Environment Variables and Secrets
- What the Pipeline Registers Automatically
- Pipeline Order
- Configuration Model Reference
- Database and Repository
- Pagination, Filtering, Sorting, and Paged Queries
- Optimistic Concurrency and ETags
- Storage Services
- HTTP Clients and Resilience
- Authentication and Authorization
- AuthorizeKey Attribute
- CSRF Protection
- Rate Limiting
- Idempotency
- Webhook Signature Validation
- API Versioning
- Swagger and OpenAPI with NSwag
- Health Checks
- Filters
- Middlewares
- Exceptions and Error Contracts
- Sanitizers
- Services and Accessors
- Models
- Utilities
- Constraints and Enums
- End-to-End Integration Pattern
- Operational Notes and Recommendations
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:
- Build an
ApplicationConfigurationobject. - Ensure required environment variables exist.
- Call
builder.RegisterAbsoluteWebApplicationBuilder(appConfig). - Build the app.
- Call
app.UseAbsolutePipeline(appConfig). - 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 32JWT_ISSUER: optional, defaults toAbsoluteAlgorithm.IdentityJWT_AUDIENCE: optional, defaults toAbsoluteAlgorithm.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
ApplicationConfigurationbefore 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:
UseForwardedHeaders()UseHsts()andUseHttpsRedirection()outside development- optional database initialization on startup
UseRouting()- optional
UseRateLimiter() - correlation and request metadata middleware
- exception middleware
- optional webhook signature validation middleware
- response compression
- response caching
- static files
- optional OpenAPI / Swagger UI
- authentication
- optional CSRF protection for cookie-authenticated APIs
- authorization
- optional idempotency middleware
- optional request-scoped database transaction middleware
- health endpoints
- root
GET /operational endpoint - 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 middlewareDatabasePolicies: named database registrationsUseStorage: enables object storage registrationStoragePolicies: named storage registrationsHttpClientPolicies: namedHttpClientregistrationsApiVersioningPolicy: API versioning optionsSwaggerPolicy: Swagger / OpenAPI optionsIdempotencyPolicy: request replay protection optionsConfigureAuthentication: enables authentication registrationConfigureAuthorization: enables authorization registrationAuthManifest: authentication and authorization optionsWebhookSignaturePolicies: webhook signature validation optionsUseRateLimit: enables ASP.NET Core rate limiting registrationRateLimitPolicies: named rate limiter policiesHealthCheckPolicy: health endpoint paths and payload style
DatabasePolicy
Configures a single named database registration.
Properties:
Name: key used to resolve theRepositoryDatabaseProvider:PostgreSQLorMSSQLConnectionStringName: environment variable that contains the connection stringInitializeDatabase: iftrue, initialization runs during startupInitializeAuditTable: iftrue, built-in audit table and audit triggers are created during startupInitializationScript: optional SQL executed during startup initializationMaxPoolSize: provider connection pool max sizeMinPoolSize: provider connection pool min sizeCommandTimeoutSeconds: provider timeout setting used for the connection configurationResiliencePolicy: retry / timeout / circuit breaker settings for repository execution
StoragePolicy
Configures a single named storage registration.
Properties:
Name: key used to resolve theStorageServiceStorageProvider:Minio,AzureBlob,GoogleCloud, orS3ConnectionStringName: environment variable that contains provider credentials or connection stringBucketName: target bucket or container nameGcpProjectId: required for Google Cloud bucket creation
HttpClientPolicy
Configures one named HttpClient.
Properties:
Name: name used withIHttpClientFactory.CreateClient(name)BaseAddress: optional absolute base URLTimeoutSeconds: request timeout for that clientDefaultHeaders: header dictionary applied to all requests from that clientResiliencePolicy: retry / timeout / circuit breaker behavior
AuthManifest
Controls auth handlers and authorization policy registration.
Properties:
EnableJwt: enables JWT bearer registrationEnableCookies: enables cookie authentication registrationEnableCsrfProtection: only meaningful when cookies are enabledPolicies: named authorization policies
AuthPolicy
Defines one authorization policy.
Properties:
PolicyName: policy name used by[Authorize(Policy = "...")]RequiredRoles: role list passed toRequireRole(...)RequiredClaims: claim-value pairs passed toRequireClaim(...)
ApiVersioningPolicy
Configures ASP.NET API versioning.
Properties:
Enabled: enables versioning registrationDefaultMajorVersion: default major versionDefaultMinorVersion: default minor versionAssumeDefaultVersionWhenUnspecified: iftrue, unversioned requests fall back to defaultReportApiVersions: emits supported / deprecated version headersReaders: one or more version readersQueryStringParameterName: used for query-string versioningHeaderNames: used for header versioningMediaTypeParameterName: used for media type parameter versioningEnableApiExplorer: enables versioned explorer metadata for Swagger groupingGroupNameFormat: API Explorer version group naming formatSubstituteApiVersionInUrl: used with URL-segment versioning
SwaggerPolicy
Configures NSwag document generation and optional Swagger UI exposure.
Properties:
Enabled: enables OpenAPI registration and pipeline exposureTitle: default document titleDescription: default document descriptionUseSwaggerUi: iftrue, the UI is exposedOpenApiPath: JSON route; use{documentName}when multiple documents existSwaggerUiPath: UI routePersistAuthorization: persist auth values in Swagger UIEnableTryItOut: enable live request execution in the UIDocumentMode:SingleorPerApiVersionSingleDocumentName: used when document mode isSingleSingleDocumentVersion: used when document mode isSingleDocuments: per-version document registrations for multi-document modeHeaders: extra request headers to render in docs
SwaggerDocumentDefinition
Defines one generated Swagger document.
Properties:
DocumentName: internal NSwag document registration nameApiGroupName: API Explorer group to includeVersion: version label shown in the documentTitle: optional per-document title overrideDescription: optional per-document description override
SwaggerHeaderDefinition
Defines one request header to surface in generated docs.
Properties:
Name: header nameDescription: human-readable descriptionRequired: whether the header is requiredAuthorizedOnly: iftrue, the header is only shown on operations that require authorization
IdempotencyPolicy
Configures request replay protection.
Properties:
Enabled: enables middleware and in-memory store registrationHeaderName: request header used as the idempotency keyRequireHeader: iftrue, missing key becomes a bad request for replayable methodsReplayableMethods: methods that participate in caching and replayExpirationMinutes: cache lifetimeMaximumResponseBodyBytes: maximum response size that can be cachedIncludeQueryStringInKey: iftrue, query string participates in the cache key
WebhookSignaturePolicy
Configures verification for inbound signed webhooks.
Properties:
Name: logical policy nameEnabled: enables the policyPathPrefix: any request path starting with this prefix is verifiedSecretName: environment variable containing the shared secretSignatureHeaderName: signature header nameTimestampHeaderName: timestamp header nameAlgorithm:HmacSha256orHmacSha512AllowedClockSkewSeconds: 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 metadataAlgorithm:FixedWindow,SlidingWindow,TokenBucket, orConcurrencyScope:Global,IpAddress,User,Endpoint, orApiKeyPermitLimit: used by fixed window, sliding window, and concurrency limitersWindow: limiter window or replenishment periodSegmentsPerWindow: sliding window segment countTokenLimit: token bucket maximum tokensTokensPerPeriod: token bucket replenishment size
HealthCheckPolicy
Configures health endpoint paths.
Properties:
Enabled: exposes health endpoints whentrueOverallEndpointPath: aggregate endpoint pathLivenessEndpointPath: liveness pathReadinessEndpointPath: readiness pathStartupEndpointPath: startup pathIncludeDetailedResponse: include entry-level detail in responses
ResiliencePolicy
Wraps retry, timeout, and circuit-breaker configuration for both repository and named HttpClient registrations.
Properties:
Enabled: master switchRetry: retry settingsTimeout: timeout settingsCircuitBreaker: circuit breaker settings
RetryResiliencePolicy
Properties:
Enabled: enables retriesMaxRetryAttempts: maximum number of retriesDelayStrategy:Fixed,Linear,Exponential, orCustomScheduleDelayMilliseconds: base delayDelayIncrementMilliseconds: increment used for linear strategyBackoffMultiplier: exponential multiplierDelayScheduleMilliseconds: explicit per-attempt scheduleUseExponentialBackoff: legacy compatibility flag; preferDelayStrategy
TimeoutResiliencePolicy
Properties:
Enabled: enables timeoutsTimeoutSeconds: timeout duration
CircuitBreakerResiliencePolicy
Properties:
Enabled: enables circuit breakerHandledEventsAllowedBeforeBreaking: failure thresholdDurationOfBreakSeconds: 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 typoExecuteInterpolatedAsync(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:
PagedRequestPagedResult<T>SortDescriptorFilterDescriptorRepositoryPagedQuery
PagedRequest
Properties:
PageNumber: 1-based page number, default1PageSize: page size, default25SearchTerm: optional free-text search termSorts: list ofSortDescriptorFilters: list ofFilterDescriptorOffset: computed zero-based row offset
SortDescriptor
Properties:
Field: logical field name requested by the clientDirection:AscendingorDescending
FilterDescriptor
Properties:
Field: logical field nameOperator:Equals,NotEquals,Contains,StartsWith,EndsWith,GreaterThan,GreaterThanOrEqual,LessThan,LessThanOrEqual,In,BetweenValue: primary value for unary filtersValues: additional values forInandBetween
RepositoryPagedQuery
This model is how you whitelist search, filter, and sort behavior.
Properties:
SelectSql: trusted baseSELECTSQL, without final paging clauseCountSql: trusted base count SQL matching the same source rowsDefaultOrderBy: default sort expression orORDER BYclauseSortColumns: map from client-facing sort field names to trusted SQL expressionsFilterColumns: map from client-facing filter field names to trusted SQL expressionsSearchColumns: 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:
RepositoryOptimisticUpdateDefinitionRepositoryOptimisticUpdateResultETagUtilityOptimisticConcurrencyUtility
RepositoryOptimisticUpdateDefinition
Properties:
ResourceName: used in generated error messagesUpdateSql: update SQL; should include expected version matching in theWHEREclauseExistsSql: optional existence check SQLCurrentVersionSql: optional SQL used to read the current version tokenRequireIfMatchHeader: iftrue, request must provide a matchingIf-Matchheader
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:
RowsAffectedCurrentVersionTokenCurrentEtag
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-MatchandIf-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 Modifieddecision support viaIf-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 nameBase64Content: optional base64 payloadByteArrayContent: optional raw bytesContentType: MIME type, defaultapplication/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, and5xx
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_SECRETwith at least 32 characters
Optional environment variables:
JWT_ISSUERJWT_AUDIENCE
Defaults:
- issuer:
AbsoluteAlgorithm.Identity - audience:
AbsoluteAlgorithm.Apps
Cookie behavior
The package registers a cookie named AbsoluteAuth with:
HttpOnly = trueSameSite = StrictSecurePolicy = 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
401with 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 == trueAuthManifest.EnableCsrfProtection == true
Behavior:
- on safe methods (
GET,HEAD,OPTIONS,TRACE), the middleware generates antiforgery tokens and writes a readableXSRF-TOKENcookie - on mutation methods, the middleware validates the request token for cookie-authenticated requests that are not using bearer tokens
- failures become
E403with 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:
FixedWindowSlidingWindowTokenBucketConcurrency
Supported scopes:
GlobalIpAddressUserEndpointApiKey
Rejected requests return a standardized 429 response with:
errorCode = E429errorMessage = "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
2xxresponses are cached - responses larger than
MaximumResponseBodyBytesare 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, otherwiseanonymous) - 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:
QueryStringHeaderMediaTypeUrlSegment
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, andstartup - 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
E400for malformed body / JSON problems - returns
E422for 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-idexists - 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
ApiExceptionto 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:
ErrorCodeErrorMessage
Throw it directly or use the factory helpers in ApiExceptions.
ApiExceptions helper
Factory members:
ApiExceptions.UnauthorizedApiExceptions.ForbiddenApiExceptions.Notfound(entity)ApiExceptions.Badrequest(message)ApiExceptions.PreconditionFailed(message)ApiExceptions.Conflict(message)ApiExceptions.FromCode(code, message)
ApiResponse<T>
Standard response envelope:
IsSuccessDataError
ErrorResponse
Error payload fields:
ErrorCodeErrorMessageValidationErrors
ValidationErrorDetail
Fields:
FieldMessages
Error code catalog
E400: bad requestE401: unauthorizedE403: forbiddenE404: not foundE409: conflictE410: goneE412: precondition failedE422: unprocessable entityE429: too many requestsE499: operation cancelledE500: 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:
PrincipalUserIdEmailIsAuthenticatedRoles
Default implementation:
HttpContextCurrentUserAccessor
IRequestMetadataAccessor
Provides current normalized request metadata through:
Current
Default implementation:
HttpContextRequestMetadataAccessor
RequestMetadata
Fields:
CorrelationIdUserIdTenantIdIdempotencyKeyClientIpAddressUserAgentPathMethodIsAuthenticated
IIdempotencyStore
Interface used by the idempotency middleware.
Default implementation:
InMemoryIdempotencyStore
Support models:
IdempotencyLookupResultIdempotencyStoredResponse
Models
This section is the condensed model catalog for the library.
Auth Models
AuthManifest: enables JWT, cookies, CSRF, and holds authorization policy definitionsAuthPolicy: named authorization policy made of roles and claim requirements
Configuration Models
ApplicationConfiguration: top-level feature switchboardApiVersioningPolicy: API versioning behavior
Database Models
DatabasePolicy: database registration optionsRepositoryPagedQuery: trusted paged query descriptorRepositoryOptimisticUpdateDefinition: optimistic update descriptorRepositoryOptimisticUpdateResult: optimistic update result
Documentation Models
SwaggerPolicy: NSwag and Swagger UI configurationSwaggerDocumentDefinition: one generated document definitionSwaggerHeaderDefinition: 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
PagedRequestPagedResult<T>SortDescriptorFilterDescriptor
Rate Limit Models
RateLimitPolicy
Request Models
RequestMetadata
Response Models
ApiResponse<T>ErrorResponseValidationErrorDetail
Resilience Models
ResiliencePolicyRetryResiliencePolicyTimeoutResiliencePolicyCircuitBreakerResiliencePolicy
Storage Models
StoragePolicyFileContent
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-idx-user-agentx-ip-addressUser-Agentx-tenant-idx-idempotency-keyx-api-keyx-idempotency-replayedx-csrf-tokenx-signaturex-signature-timestampETagIf-MatchIf-None-Match
Database constraint key
DATABASE.CONNECTIONSKEY is the HttpContext.Items key used for request-scoped database connections and transactions.
Core enums
DatabaseProvider
PostgreSQLMSSQL
StorageProvider
MinioAzureBlobGoogleCloudS3
RateLimitAlgorithm
FixedWindowSlidingWindowTokenBucketConcurrency
RateLimitScope
GlobalIpAddressUserEndpointApiKey
ApiVersionReaderType
QueryStringHeaderMediaTypeUrlSegment
SwaggerDocumentMode
SinglePerApiVersion
SortDirection
AscendingDescending
FilterOperator
EqualsNotEqualsContainsStartsWithEndsWithGreaterThanGreaterThanOrEqualLessThanLessThanOrEqualInBetween
RequestSignatureAlgorithm
HmacSha256HmacSha512
RetryDelayStrategy
FixedLinearExponentialCustomSchedule
ResilienceType
RetryTimeoutCircuitBreaker
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:
- Put infrastructure settings into
ApplicationConfiguration. - Put secrets into environment variables.
- Register the package once in
Program.cs. - Resolve keyed
Repositoryand keyedStorageServiceinstances where needed. - Resolve named
HttpClientinstances withIHttpClientFactory. - Use
[Authorize],[Authorize(Policy = "...")], and[AuthorizeKey(...)]on endpoints. - 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
QueryInterpolatedAsyncandExecuteInterpolatedAsyncfor 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
AuthorizeKeyAttributefor machine-to-machine endpoints that rely on API keys. - Use
SwaggerPolicy.Headersto 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
InMemoryIdempotencyStorewith a centralized implementation. - If startup schema creation is too risky for your environment, leave
InitializeDatabasedisabled and manage schema migrations separately. - Keep
JWT_SECRETstrong 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 | Versions 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. |
-
net10.0
- Asp.Versioning.Mvc (>= 8.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.0)
- AspNetCore.HealthChecks.Aws.S3 (>= 9.0.0)
- AspNetCore.HealthChecks.AzureStorage (>= 7.0.0)
- AspNetCore.HealthChecks.NpgSql (>= 9.0.0)
- AspNetCore.HealthChecks.SqlServer (>= 9.0.0)
- AWSSDK.S3 (>= 4.0.19)
- Azure.Storage.Blobs (>= 12.23.0)
- CsvHelper (>= 33.0.1)
- Dapper (>= 2.1.72)
- Google.Cloud.Storage.V1 (>= 4.10.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.5)
- Microsoft.Data.SqlClient (>= 6.1.4)
- Minio (>= 7.0.0)
- Minio.AspNetCore.HealthChecks (>= 6.0.1)
- NLog.Web.AspNetCore (>= 6.1.0)
- Npgsql (>= 10.0.1)
- NSwag.AspNetCore (>= 14.6.3)
- NSwag.Generation.AspNetCore (>= 14.6.3)
- Polly (>= 8.6.6)
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 |
|---|