MarsArmories.AmbientContext 2.0.0

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

AmbientContext

CI NuGet

AmbientContext is a .NET 8 and .NET 10 library for strongly typed ambient values over AsyncLocal<T>. The runtime owns the scoped state, while the source generator creates named APIs such as IClientIdContext, ITenantIdAccessor, and optional aggregate registration helpers.

Installation

Install the MarsArmories.AmbientContext package:

dotnet package add MarsArmories.AmbientContext

The package includes the source generator.

Install the optional MarsArmories.AmbientContext.Analyzers package for diagnostics around fire-and-forget work:

dotnet package add MarsArmories.AmbientContext.Analyzers

Basic Usage

Define contexts with assembly attributes:

using AmbientContext.Abstractions;

[assembly: AmbientContext(typeof(Guid), "ClientId")]
[assembly: AmbientContext(typeof(string), "TenantId")]
[assembly: AmbientContextRegistration("AddMyAppAmbientContexts")]

Register the generated services:

services.AddMyAppAmbientContexts();

The optional AmbientContextRegistration attribute generates one aggregate registration method for the assembly. When the attribute is omitted, no aggregate method is generated. Individual methods such as AddClientIdContext() and AddTenantIdContext() are always available.

Generic host applications can register all generated contexts directly on IHostBuilder:

Host.CreateDefaultBuilder(args)
    .AddMyAppAmbientContexts();

Inject the generated context and accessor interfaces:

public sealed class Worker
{
    private readonly IClientIdContext _clientIdContext;
    private readonly IClientIdAccessor _clientIdAccessor;

    public Worker(IClientIdContext clientIdContext, IClientIdAccessor clientIdAccessor)
    {
        _clientIdContext = clientIdContext;
        _clientIdAccessor = clientIdAccessor;
    }

    public Task RunAsync(Guid clientId, CancellationToken cancellationToken)
    {
        return _clientIdContext.ExecuteAsClientIdAsync(clientId, ct =>
        {
            Console.WriteLine(_clientIdAccessor.Current);
            return Task.CompletedTask;
        }, cancellationToken);
    }
}

Generated context interfaces also provide synchronous ExecuteAs* overloads for Action and Func<TResult>. Asynchronous execution accepts Task-returning delegates, so ordinary async lambdas compile without delegate casts.

Reading Current

Current returns the value for the active ambient scope. Accessing Current outside a matching ExecuteAs* or ExecuteAs*Async scope throws AmbientContextMissingException.

TryGetCurrent and CurrentOrDefault are available for optional reads. Generated value-type accessors expose nullable CurrentOrDefault, for example Guid?.

Null Values

Ambient values are non-null. Passing null for a reference-type context throws AmbientContextNullValueException.

Nested Scopes

Nested scopes restore the previous value when they complete. Different contexts are isolated by generated marker types, so ClientId and CorrelationId can both use Guid without colliding.

Exceptions inside ExecuteAs*Async do not leak context state. The previous value is restored through finally/Dispose behavior.

ExecutionContext Flow

AmbientContext uses AsyncLocal<T>. AsyncLocal<T> values flow with .NET's ExecutionContext.

This means work started inside an ExecuteAs*Async scope may capture the current ambient value:

await clientIdContext.ExecuteAsClientIdAsync(clientId, async ct =>
{
    _ = Task.Run(ProcessLaterAsync);
}, cancellationToken);

The queued task may still observe the ClientId even after ExecuteAsClientIdAsync has completed. This is expected .NET behavior.

For fire-and-forget work that must not inherit ambient context, suppress ExecutionContext flow:

await clientIdContext.ExecuteAsClientIdAsync(clientId, async ct =>
{
    using (ExecutionContext.SuppressFlow())
    {
        _ = Task.Run(ProcessLaterAsync);
    }
}, cancellationToken);

ExecutionContext.SuppressFlow prevents AsyncLocal<T>, culture, security context, and other execution-context data from flowing into newly queued asynchronous work.

Only suppress flow around the scheduling operation.

Correct:

using (ExecutionContext.SuppressFlow())
{
    _ = Task.Run(ProcessLaterAsync);
}

Incorrect:

using (ExecutionContext.SuppressFlow())
{
    await SomethingAsync();
}

Suppressing flow across await can cause problems because AsyncFlowControl must be restored on the same logical flow where it was created.

Recommended helper:

AmbientContextFlow.SuppressFor(() =>
{
    _ = Task.Run(ProcessLaterAsync);
});

Analyzer Warnings

AmbientContext.Analyzers reports obvious fire-and-forget scheduling inside ExecuteAs*Async lambdas.

The current analyzer rule inventory is maintained in:

Generator diagnostics are maintained in the generator diagnostics inventory.

Migrating From 1.x

Version 2.0 intentionally reduces the public API surface:

  • Inject generated I{Name}Context interfaces instead of concrete {Name}Context classes.
  • Replace AddAmbientContext() with selective Add{Name}Context() calls, or opt into a uniquely named aggregate method with AmbientContextRegistrationAttribute.
  • Replace ValueTask delegates with Task-returning delegates.
  • Use AmbientContextRuntime<TContext, TValue> when directly consuming the low-level runtime API.

Observability

AmbientContext does not emit OpenTelemetry spans, scopes, or metrics by default.

Ambient values are often tenant, client, user, correlation, or workflow identifiers. Automatically attaching those values to spans or meter tags can create high-cardinality telemetry, increase export cost, and accidentally expose sensitive application data.

Prefer adding metrics and spans at the application boundary where the ambient value has domain meaning. Consumer code can read generated accessors such as IClientIdAccessor and decide which values are safe to record, which should be hashed or bucketed, and which should never leave process memory.

Integration Patterns

See the integration patterns guide for:

  • ASP.NET Core middleware and endpoint filters.
  • GraphQL request and resolver middleware.
  • GraphQL DataLoader batching considerations.
  • Message envelope and parallel-consumer patterns.
  • Multiple ambient values and fire-and-forget work.

Resources

Please use GitHub Issues for bugs and feature requests. Report security vulnerabilities privately as described in the security policy.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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

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
2.0.0 112 6/5/2026
1.1.0 109 6/5/2026
1.0.0 111 6/1/2026
0.1.0-alpha.3 53 5/31/2026
0.1.0-alpha.2 60 5/31/2026