Skip to main content

Shared libraries

TagShape.Common

Referenced by every service. It holds the things that must not be allowed to differ between them.

Wiring

Two extension methods do almost all of a service's setup:

builder.AddCSMCommonWeb(options =>
{
options.OpenTelemetrySource = "TagShape.Api.LibrarySet.Web";
options.OpenApiEnabled = true;
options.OpenApiJwtAuthOption = true;
options.IncludeRecordAuthors = true;
options.DiscoverableServices = ["api-audit", "api-account"];
});

// …

app.UseCSMCommonWebTop(options => { options.OpenApiEnabled = true; options.OpenApiJwtAuthOption = true; });
app.UseCSMCommonWebBottom();
CommonOptionsDefault
OpenTelemetrySourceTagShape.CommonTelemetry source name
OtlpEndpointWhere traces go
MetersExtra meters to collect
OpenApiEnabledfalseServe /openapi/v1.json
OpenApiTitlev1
OpenApiJwtAuthOptionfalseAdd a JWT auth option to the OpenAPI UI
DiscoverableServices(empty)Aspire service names to register clients for
IncludeGenericHttpClienttrue
IncludeRecordAuthorsfalseWire up IRecordAuthors; wants Account:ApiUrl

UseCSMCommonWebTop also maps /healthz/startup, /healthz/liveness and /healthz/readiness.

Authentication

Type
PermissionThe enum. The single source of truth for what exists.
PermissionInfoAttributeCategory, description, global flag and dependencies, per permission
PermissionExtensionsHasPermissions, CanGrant, WithDependencies, UngrantablePermissions, IsGlobal
IJwtPayload / JwtPayloadReads the token per request into UserId, OrgId, Email, Name, Permissions
JwtRequestExtensionsWhere a token and a key are read from — x-tagshape-auth, x-tagshape-api
AuthenticationProviderEntra and Google, serialised by name

The permission info and dependency closure are computed once at type initialisation, not per request — the descriptions never change at runtime, and every permission check walks the graph.

Middleware

PermissionsMiddleware enforces [RequiresPermissions] wherever it appears in endpoint metadata, which covers minimal API handlers and routable Blazor components alike. Requests carrying no requirement — static files, health checks, anonymous endpoints — short-circuit before the token is read, so they do not pay for it.

Audit

IAuditHandler is how every service writes to the audit API. Configured with Audit:AuditApiUrl, which is required.

Record authors

IRecordAuthors puts names to the user IDs stamped on a service's records, by calling POST /users/names/lookup on the account API. Off by default — most services either hold the users themselves or never show who touched anything. Turning it on wants Account:ApiUrl, and it degrades to a missing name rather than a failed request when that is not set.

Models and list helpers

ListDataSetInt<T>, ListDataSetGuid<T> and ListDataSetDictionary<K,V> are the { total, items } wrappers. ListFilter is one condition on a list request. Extension methods provide ApplyFilters, ApplySort and ApplyPaging over an IQueryable, so paging and filtering behave identically wherever they are offered.

Per-service libraries

Each API splits the same way:

ProjectHolds
*.DomainModels, DTOs, mappers, and pure rules (SensitiveValueRules, ApiKeyExpiryRules, GroupPermissionRules, AuditVisibilityRules)
*.DataThe DbContext
*.MigrationsEF Core migrations, in their own assembly
*.LogicHandlers with dependencies (VariableConsumptionHandler, LibraryImportPlanner)
*.WebEndpoints and composition

Rules live in Domain precisely because they are pure — they can be tested without a database, a request or a container, which is what makes test-driven development practical here.

TagShape.Api.Identity.Jwk and .Jwt

Signing key generation and token minting, kept out of the web project so both are testable on their own. JwtGenerator mints from a login, a refresh and an API key through one private MintToken, so the three cannot drift into producing subtly different sessions.

Test harnesses

TagShape.Common.TestHarness.API and TagShape.Common.TestHarness.Web supply the shared test scaffolding. Every class has a corresponding *Tests class, and test names follow <Function>_<ExpectedOutcome>_<Condition>.