SyntaxCircus.Common 0.2.0

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

SyntaxCircus.Common

Build NuGet License: MIT

The handful of contract types and dependency-free helpers that keep getting reinvented per product: operation results, a pagination result, ClaimsPrincipal claim resolution, a periodic background service base, and a standalone sliding-window rate limiter for hosts that aren't a normal ASP.NET Core pipeline.

Since 0.2.0 the package has no web framework dependency: it references only Microsoft.Extensions.* abstractions, so console, worker, SDK and MAUI consumers do not inherit Microsoft.AspNetCore.App.

No support guaranteed. Published as-is and maintained on a best-effort basis. Issues and PRs are welcome, but there's no SLA — fork it or vendor what you need if that's not enough.

Result and Result<T>

Use transport-neutral results across application boundaries. Errors carry a stable code, a client-safe message, a semantic kind, and an optional validation target; Result/Result<T> themselves do not carry HTTP status codes — see ApiResult/ApiResult<T> below for the one case that needs to.

public async Task<Result<Widget>> HandleAsync(CreateWidgetRequest request)
{
    if (string.IsNullOrWhiteSpace(request.Name))
    {
        return Result<Widget>.Failure(new ResultError(
            "name-required",
            "A name is required.",
            ResultErrorKind.Validation,
            "name"));
    }

    var widget = await CreateAsync(request);
    return Result<Widget>.Success(widget);
}

Failures contain at least one error. Multiple errors are reserved for validation failures, and all errors in a result have the same kind. Accessing Value on a failed Result<T> throws.

ApiResult and ApiResult<T>

For the rare case where a failure needs to carry an exact upstream HTTP status code — proxying a third-party API's 429/502/503 rather than collapsing it into one of ResultErrorKind's fixed kinds — ApiResult and ApiResult<T> extend Result/Result<T> with a StatusCode:

public async Task<ApiResult<Widget>> HandleAsync(CreateWidgetRequest request)
{
    var upstream = await CallUpstreamApiAsync(request);
    if (!upstream.IsSuccess)
    {
        return ApiResult<Widget>.Failure(
            upstream.StatusCode,
            "upstream-widget-error",
            "The upstream widget service returned an error.");
    }

    return ApiResult<Widget>.Success(upstream.Widget);
}

StatusCode is null on success — success-side status selection stays the caller's responsibility, same as Result/Result<T>. Failure requires a status in the 400–599 range. The constructed error always has ResultErrorKind.Passthrough and no validation target.

PagedResult<T>

new PagedResult<Widget>(items, page: 1, pageSize: 25, totalCount: 142);
// .TotalPages, .HasPreviousPage, .HasNextPage are computed

ClaimsPrincipalExtensions

user.GetSubject();     // "sub" claim, falling back to ClaimTypes.NameIdentifier
user.GetEmail();       // "email" claim, falling back to ClaimTypes.Email
user.GetDisplayName(); // "name" claim, falling back to "preferred_username"

Moved: ICurrentUserService

ICurrentUserService, its implementation and AddCurrentUserService() live in SyntaxCircus.AspNetCore.Common 0.1.16+ (namespace SyntaxCircus.AspNetCore.Common) since 0.2.0.

PeriodicBackgroundService

public sealed class CleanupWorker(ILogger<CleanupWorker> logger)
    : PeriodicBackgroundService(TimeSpan.FromMinutes(5), logger)
{
    protected override async Task ExecuteTickAsync(CancellationToken cancellationToken)
    {
        // do the periodic work
    }
}

A BackgroundService base that runs ExecuteTickAsync on a fixed interval — one failing tick is caught and logged rather than crashing the whole service, and the delay is between ticks (not tick starts), so a slow tick can't overlap the next one.

SlidingWindowRateLimiter

var limiter = new SlidingWindowRateLimiter(permitLimit: 5, window: TimeSpan.FromMinutes(1));

if (!limiter.TryAcquire(key: remoteIpAddress))
{
    // reject
}

A plain, key-based sliding-window limiter with no HttpContext or middleware dependency — for hosts that aren't a normal ASP.NET Core request pipeline (an embedded server, a SignalR hub, a background worker) where System.Threading.RateLimiting's middleware integration doesn't apply.

Contributing

Since 0.2.0 this package has no web framework dependency, and ICurrentUserService moved to SyntaxCircus.AspNetCore.Common. The web-neutral contracts proposal is kept as history.

Issues and pull requests are welcome:

  • Keep changes focused, with a clear description of the behavior change.
  • Match the existing code style (see .editorconfig).
  • Call out any breaking changes to the public API in your PR description.

License

MIT — see LICENSE.txt.

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 (6)

Showing the top 5 NuGet packages that depend on SyntaxCircus.Common:

Package Downloads
SyntaxCircus.AspNetCore.Common

Small, near-universal ASP.NET Core host boilerplate: result-to-ProblemDetails mapping, correlation-ID middleware, security headers, exception handling, and a current-user service.

SyntaxCircus.AspNetCore.Common.MassTransit

Optional companion to SyntaxCircus.AspNetCore.Common: MassTransit consume/publish/send filters that propagate the configured correlation ID across message-bus boundaries, keeping log enrichment consistent with the HTTP middleware.

SyntaxCircus.Cmsify.Core

Cmsify domain entities, validation, service contracts, and direct workspace request handlers.

SyntaxCircus.Cmsify.Infrastructure

Cmsify PostgreSQL persistence and migrations, repositories, storage, host integration, audit, and background services.

SyntaxCircus.Cmsify.Infrastructure.Sqlite

Optional SQLite persistence registration and schema-only migrations for Cmsify.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 116 10/7/2026
0.1.4 100 9/30/2026
0.1.3 1,707 8/29/2026
0.1.2 149 8/28/2026
0.1.1 124 8/16/2026