Usage Cookbook¶
Practical patterns for real services using ContextR.
Pattern: required vs optional mapping¶
Use Required() for fields that must be present and valid:
ctx.Add<UserContext>(reg => reg
.Map(m => m
.Property(c => c.TraceId, "X-Trace-Id").Required()
.Property(c => c.TenantId, "X-Tenant-Id").Required()
.Property(c => c.UserId, "X-User-Id").Optional()));
Pattern: infer required/optional from nullability¶
ctx.Add<UserContext>(reg => reg
.Map(m => m
.ByConvention()
.Property(c => c.TraceId, "X-Trace-Id")
.Property(c => c.TenantId, "X-Tenant-Id")
.Property(c => c.UserId, "X-User-Id")));
Or explicit per-property:
ctx.Add<UserContext>(reg => reg
.Map(m => m
.Property(c => c.TraceId, "X-Trace-Id").ByConvention()
.Property(c => c.TenantId, "X-Tenant-Id").ByConvention()
.Property(c => c.UserId, "X-User-Id").ByConvention()));
Explicit Required() / Optional() always override conventions.
Use fully manual mode when needed:
ctx.Add<UserContext>(reg => reg
.DisableNullabilityConventions()
.Map(m => m
.Property(c => c.TraceId, "X-Trace-Id").Required()
.Property(c => c.TenantId, "X-Tenant-Id").Required()
.Property(c => c.UserId, "X-User-Id").Optional()));
Pattern: non-primitive property mapping¶
For List<T>, arrays, or custom classes:
ctx.Add<UserContext>(reg => reg
.UseInlineJsonPayloads<UserContext>(o =>
{
o.MaxPayloadBytes = 4096;
o.OversizeBehavior = ContextOversizeBehavior.FailFast;
})
.MapProperty(c => c.Roles, "X-Roles")
.MapProperty(c => c.Profile, "X-Profile"));
Pattern: oversize strategy matrix¶
| Behavior | Inject | Extract | Typical use |
|---|---|---|---|
FailFast |
throws | throws | strict contracts |
SkipProperty |
drops property | property missing | non-critical metadata |
ChunkProperty |
writes chunk keys | reassembles chunks | bigger metadata in headers/metadata |
FallbackToToken |
token fallback path | token lookup path | very large payloads with external store |
Pattern: hybrid policy precedence¶
Oversize behavior selection order:
- property override (
OversizeBehavior(...)/MapProperty(..., override)) - mapping default (
DefaultOversizeBehavior(...)) - runtime strategy policy (
UseStrategyPolicy(...)) - transport default (
UseInlineJsonPayloads(...).OversizeBehavior) FailFast
Pattern: strategy policy service¶
ctx.Add<UserContext>(reg => reg
.UseInlineJsonPayloads<UserContext>(o => o.MaxPayloadBytes = 256)
.UseChunkingPayloads<UserContext>()
.UseStrategyPolicy<UserContext, UserStrategyPolicy>()
.MapProperty(c => c.Roles, "X-Roles")
.MapProperty(c => c.Profile, "X-Profile"));
Pattern: strategy policy delegate from DI¶
ctx.Add<UserContext>(reg => reg
.UseInlineJsonPayloads<UserContext>(o => o.MaxPayloadBytes = 256)
.UseChunkingPayloads<UserContext>()
.UseStrategyPolicy<UserContext>(sp => policyContext =>
{
return policyContext.Key == "X-Roles"
? ContextOversizeBehavior.ChunkProperty
: ContextOversizeBehavior.SkipProperty;
})
.MapProperty(c => c.Roles, "X-Roles")
.MapProperty(c => c.Profile, "X-Profile"));
Pattern: domain-aware failure handling¶
ctx.Add<UserContext>(reg => reg
.OnPropagationFailure<UserContext>(_ => PropagationFailureAction.Throw));
ctx.AddDomain("public-api", d => d.Add<UserContext>(reg => reg
.OnPropagationFailure<UserContext>(_ => PropagationFailureAction.SkipProperty)));
Pattern: ASP.NET Core ingress enforcement¶
ctx.Add<UserContext>(reg => reg
.Map(m => m
.ByConvention()
.Property(c => c.TenantId, "X-Tenant-Id")
.Property(c => c.TraceId, "X-Trace-Id"))
.UseAspNetCore(o => o.Enforcement(e =>
{
e.Mode = ContextIngressEnforcementMode.FailRequest;
e.OnFailure = failure => ContextIngressFailureDecision.Fail(400, "Tenant context is required.");
})));
DI-aware variant:
ctx.Add<UserContext>(reg => reg
.Map(m => m.ByConvention().Property(c => c.TenantId, "X-Tenant-Id"))
.UseAspNetCore((sp, o) =>
{
var logger = sp.GetRequiredService<ILogger<Startup>>();
o.Enforcement(e =>
{
e.Mode = ContextIngressEnforcementMode.ObserveOnly;
e.OnFailure = f =>
{
logger.LogWarning("Context ingress issue: {Reason}", f.Reason);
return ContextIngressFailureDecision.Continue();
};
});
}));
Pattern: background processing with snapshots¶
var snapshot = accessor.CreateSnapshot();
_ = Task.Run(async () =>
{
using (snapshot.BeginScope())
{
await processor.RunAsync();
}
});
This avoids accidental coupling to request pipeline state and keeps propagation deterministic.