Canary.AspNetCore
1.3.0
dotnet add package Canary.AspNetCore --version 1.3.0
NuGet\Install-Package Canary.AspNetCore -Version 1.3.0
<PackageReference Include="Canary.AspNetCore" Version="1.3.0" />
<PackageVersion Include="Canary.AspNetCore" Version="1.3.0" />
<PackageReference Include="Canary.AspNetCore" />
paket add Canary.AspNetCore --version 1.3.0
#r "nuget: Canary.AspNetCore, 1.3.0"
#:package Canary.AspNetCore@1.3.0
#addin nuget:?package=Canary.AspNetCore&version=1.3.0
#tool nuget:?package=Canary.AspNetCore&version=1.3.0
Canary.AspNetCore
Cross-cutting middleware + validation primitives for the E2E canary protocol — the mechanism that lets a Playwright run exercise a live deployment without its data colliding with real customer data or hitting real third-party APIs.
Shared NuGet package consumed by every backend service in the stack (IdentityService, PaymentService, NotificationService, OnlineMenuService, QuestionerService, ContentService).
What it does
- Gates the
X-Canary-Run-Idheader on a validsuperUserJWT. The header alone does nothing — without the role it gets HTTP 403INVALID_CANARY_AUTH. With the role, the request is tagged for downstream behavioral mocks (PaymentService skips Stripe, NotificationService suppresses SMTP/SMS). - Rejects reserved-prefix names at the validator layer. Any user-controlled
name field that the cleanup endpoint sweeps by prefix (
e2ec-{runId8}-*) must refuse matching input so real customer data cannot collide with the canary sweep. - Provides a per-request canary accessor (
ICanaryRunContext) that endpoints inject when they need to branch on canary state.
Install
<PackageReference Include="Canary.AspNetCore" Version="1.0.0" />
Public API surface
| Type | Purpose |
|---|---|
AddCanaryAuth(IConfiguration, Action<CanaryOptions>?) |
DI registration — binds CanaryOptions + registers the scoped ICanaryRunContext. |
UseCanaryAuth() |
Pipeline registration — adds CanaryAuthMiddleware. Must run after UseAuthorization(). |
ICanaryRunContext |
Scoped per-request accessor: IsCanary, RunId, RunIdShort. Inject this into endpoints. |
CanaryRunContext |
Concrete implementation of ICanaryRunContext. Public so middleware-level tests can construct it without InternalsVisibleTo; endpoints should still depend on the interface. |
CanaryAuthMiddleware |
The header-gating middleware. Registered via UseCanaryAuth(). |
CanaryOptions |
Bound to the Canary config section — HeaderName, RequiredRole, ReservedPrefixPattern. All have sensible defaults. |
CanaryErrorCodes |
Stable error-code constants: RESERVED_PREFIX, INVALID_CANARY_AUTH, INVALID_CANARY_RUN_ID. |
MustNotMatchReservedPrefix<T, TProperty>(CanaryOptions?) |
FluentValidation rule extension — rejects reserved-prefix input with code RESERVED_PREFIX. |
Pipeline placement (CRITICAL)
UseCanaryAuth() MUST be registered after UseAuthorization(). Registering
it earlier would let unauthenticated callers probe canary behavior via
response-timing differences. The class-level remarks on CanaryAuthMiddleware
document this in detail.
app.UseAuthentication();
app.UseAuthorization();
app.UseCanaryAuth(); // <- AFTER authorization
app.UseRateLimiter();
app.UseFastEndpoints(...);
Consumer-side registration
// Program.cs (or ProgramExtensions.cs)
builder.Services.AddCanaryAuth(builder.Configuration);
// ... app build ...
app.UseAuthentication();
app.UseAuthorization();
app.UseCanaryAuth();
Reserved-prefix validation
Add to every FluentValidation validator rule that names user-controlled fields the cleanup endpoint sweeps:
using Canary.AspNetCore.Validation;
public class CreateMenuRequestValidator : Validator<CreateMenuRequest>
{
public CreateMenuRequestValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(100)
.MustNotMatchReservedPrefix(); // <- adds RESERVED_PREFIX rule
}
}
Failing input produces a 400 with error code RESERVED_PREFIX.
Endpoint-side usage of the canary context
public class CreateSubscription(ICanaryRunContext canary) : Endpoint<...>
{
public override async Task HandleAsync(...)
{
if (canary.IsCanary)
{
// Skip Stripe; write a mock subscription tagged with canary.RunIdShort
}
else
{
// Normal Stripe flow
}
}
}
Configuration
The Canary section binds to CanaryOptions. All settings have sensible
defaults; only override when the service has a genuinely different protocol.
{
"Canary": {
"HeaderName": "X-Canary-Run-Id",
"RequiredRole": "superUser",
"ReservedPrefixPattern": "^e2ec-[0-9a-f]{8}-"
}
}
Observability
When canary mode activates, the middleware:
- Pushes a Serilog
LogContextpropertycanary_run_idfor the duration of the request — all downstream log lines carry the property automatically. - Echoes the
X-Canary-Run-Idheader back on the response so the E2E runner can confirm its header was honored.
Prometheus labeling lives outside this library (the service-level metrics
middleware in Metrics.Client reads ICanaryRunContext at scrape time).
Cleanup endpoint pattern (per-service)
This library does not ship the cleanup endpoint — each service owns its own because the entities differ. The recommended shape:
DELETE /api/v1/internal/canary-cleanup?runId={uuid}
Authorization: Bearer <superUser JWT>
Response: { "usersDeleted": N, "tenantsDeleted": M, ... }
Cleanup endpoints MUST:
- Require the
superUserrole. - Validate
runIdis a UUID (regex^[0-9a-f-]{36}$) to refuse arbitrary prefixes being interpreted asLIKEpatterns. - Be idempotent — re-runs return 0 counts, not 404.
- Delete by the
e2ec-{runId8}-name prefix whererunId8is the first 8 hex chars of the supplied UUID.
License
MIT
| 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
- FluentValidation (>= 12.0.0)
- Serilog.AspNetCore (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Canary.AspNetCore:
| Package | Downloads |
|---|---|
|
Metrics.Client
Lightweight Prometheus metrics for ASP.NET Core services. Auto-collects HTTP request duration, request count, and active requests with low-cardinality labels. |
GitHub repositories
This package is not used by any popular GitHub repositories.