Skip to content

Aspire integration

CaeriusNet provides first-class integration with .NET Aspire through WithAspireSqlServer and WithAspireRedis. Aspire manages SQL Server and Redis as named resources in the AppHost; CaeriusNet resolves their connection strings automatically.

This page also documents the OpenTelemetry signals that CaeriusNet emits, regardless of whether you use Aspire — you only need to register them with your telemetry pipeline.

Prerequisites

  • .NET 10 and a .NET Aspire AppHost project
  • The CaeriusNet NuGet package in your service project
  • SQL Server and (optionally) Redis resources declared in the AppHost

AppHost configuration

Declare SQL Server and Redis in the AppHost and pass their references to your service:

csharp
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

var sql = builder.AddSqlServer("sqlserver")
    .AddDatabase("MyAppDb");

var redis = builder.AddRedis("redis");

builder.AddProject<Projects.MyApp_Api>("api")
    .WithReference(sql)
    .WithReference(redis);

builder.Build().Run();

Service project configuration

In your service's Program.cs, use WithAspireSqlServer and WithAspireRedis. These methods resolve the connection string from Aspire's named connection registry:

csharp
using CaeriusNet.Builders;

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); // Aspire ServiceDefaults

CaeriusNetBuilder
    .Create(builder)
    .WithAspireSqlServer("sqlserver")  // matches the AppHost resource name
    .WithAspireRedis("redis")          // optional distributed cache
    .Build();

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();
csharp
using CaeriusNet.Builders;

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

CaeriusNetBuilder
    .Create(builder)
    .WithAspireSqlServer("sqlserver")
    .Build();

var app = builder.Build();
app.Run();

Console and worker service pattern

For console apps or background workers running under Aspire:

csharp
using CaeriusNet.Builders;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();

CaeriusNetBuilder
    .Create(builder)
    .WithAspireSqlServer("sqlserver")
    .WithAspireRedis("redis")
    .Build();

var host = builder.Build();
host.Run();

Manual setup (without Aspire)

If you are not using Aspire, use WithSqlServer and WithRedis with explicit connection strings:

csharp
CaeriusNetBuilder
    .Create(services)
    .WithSqlServer(configuration.GetConnectionString("Default")!)
    .WithRedis("localhost:6379")  // optional
    .Build();

Resource-name matching

The string passed to WithAspireSqlServer and WithAspireRedis must match the resource name declared in the AppHost:

AppHost declarationService builder call
builder.AddSqlServer("sqlserver").WithAspireSqlServer("sqlserver")
builder.AddRedis("redis").WithAspireRedis("redis")

Default names

If you use the conventional names "sqlserver" and "redis", you can also call the parameter-less overloads:

csharp
.WithAspireSqlServer()  // defaults to "sqlserver"
.WithAspireRedis()      // defaults to "redis"

Complete example

csharp
// AppHost/Program.cs
var sql   = builder.AddSqlServer("sqlserver").AddDatabase("CaeriusDb");
var redis = builder.AddRedis("redis");
builder.AddProject<Projects.Api>("api")
    .WithReference(sql)
    .WithReference(redis);
csharp
// Api/Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

CaeriusNetBuilder
    .Create(builder)
    .WithAspireSqlServer("sqlserver")
    .WithAspireRedis("redis")
    .Build();

builder.Services.AddScoped<IUserRepository, UserRepository>();

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();
csharp
// Api/Repositories/UserRepository.cs
public sealed record UserRepository(ICaeriusNetDbContext DbContext)
    : IUserRepository
{
    public async Task<IEnumerable<UserDto>> GetAllAsync(CancellationToken ct)
    {
        var sp = new StoredProcedureParametersBuilder("dbo", "usp_Get_All_Users", 250)
            .AddRedisCache("users:all", TimeSpan.FromMinutes(5))
            .Build();

        return await DbContext.QueryAsIEnumerableAsync<UserDto>(sp, ct);
    }
}

Telemetry options

Use WithTelemetryOptions to configure how CaeriusNet records spans and metrics. Options are applied globally and consulted by every command pipeline.

csharp
CaeriusNetBuilder.Create(builder)
    .WithAspireSqlServer("sqlserver")
    .WithAspireRedis()
    .WithTelemetryOptions(new CaeriusTelemetryOptions
    {
        // Include parameter names AND values in caerius.sp.parameters.
        // ⚠ Enable only outside production — values may contain PII
        //   (names, emails, tokens, monetary amounts, etc.).
        CaptureParameterValues = true
    })
    .Build();
OptionTypeDefaultDescription
CaptureParameterValuesboolfalseWhen true, the caerius.sp.parameters tag shows @name=value pairs instead of just @name. TVP values are always shown as [TVP].

Production guidance

CaptureParameterValues = true is convenient in staging or development to correlate a trace with the exact parameters that produced it. In production, parameter values can contain sensitive data (user IDs, emails, amounts …) and should generally not be emitted to a shared telemetry backend.

Tracing and telemetry

CaeriusNet emits OpenTelemetry-compatible signals through the BCL primitives — no OpenTelemetry SDK package is added to the library itself. Consumers (typically the Aspire ServiceDefaults project) opt in by registering the source and meter:

csharp
// ServiceDefaults/Extensions.cs
using CaeriusNet.Telemetry;

builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddSource(CaeriusDiagnostics.SourceName))   // "CaeriusNet"
    .WithMetrics(m => m.AddMeter (CaeriusDiagnostics.SourceName));  // "CaeriusNet"

When no listener is subscribed, no allocation is performed.

Spans

Every stored procedure call creates an Activity of ActivityKind.Client named SP {schema}.{procedure}. Failures set ActivityStatusCode.Error and attach the SQL exception via Activity.AddException.

TagDescription
db.systemAlways mssql (OpenTelemetry semantic convention)
db.operationThe calling command (FirstQueryAsync, QueryMultipleImmutableArrayAsync, ExecuteNonQueryAsync, …)
db.statement{schema}.{procedure}
caerius.sp.schemaSchema of the stored procedure
caerius.sp.nameName of the stored procedure
caerius.sp.parametersComma-separated parameter names (e.g. @id,@tvp); shows @name=value when CaptureParameterValues = true. TVP values always render as [TVP].
caerius.sp.commandSame as db.operation (kept for filter convenience)
caerius.tvp.usedtrue when at least one TVP is attached
caerius.tvp.type_nameTVP type name (e.g. dbo.tvp_int); comma-separated when several TVPs are used
caerius.resultset.multitrue when more than one result set is requested
caerius.resultset.expected_countNumber of result sets requested (1 by default; 2/3/4/5 for multiple-result overloads)
caerius.cache.tier / caerius.cache.hitSet on the active span when a cache lookup happens during the call
caerius.txtrue when the call runs inside an ICaeriusNetTransaction
caerius.rows_returned / caerius.rows_affectedSet on success

Metrics

Four instruments are exposed by the CaeriusNet meter, all tagged with the same caerius.sp.* dimensions as the spans:

InstrumentTypeUnitPurpose
caerius.sp.durationHistogrammsStored procedure execution duration
caerius.sp.executionsCountercallsNumber of executions started (success or failure)
caerius.sp.errorsCountercallsNumber of executions that failed with a SQL error
caerius.cache.lookupsCounterlookupsCache lookups, tagged with caerius.cache.tier (Frozen / InMemory / Redis) and caerius.cache.hit (true / false)

When a cache hit short-circuits the SQL call, no DB span is created and only caerius.cache.lookups{hit=true} is emitted — the Aspire dashboard accurately shows the database was not contacted.

Transaction tracing

Every ICaeriusNetTransaction scope emits a parent TX span (kind = Internal) that wraps all child stored procedure spans. This produces a single cohesive trace in the Aspire dashboard:

text
TX  (kind=Internal, caerius.tx.isolation_level=ReadCommitted, caerius.tx.outcome=committed)
├── SP Users.usp_Create_User  (kind=Client, caerius.tx=true)
└── SP Users.usp_Create_Order (kind=Client, caerius.tx=true)
TagDescription
caerius.tx.isolation_levelThe SQL Server isolation level (e.g. ReadCommitted)
caerius.tx.outcomecommitted, rolled-back, auto-rollback, poisoned-auto-rollback, commit-failed, rollback-failed

SQL-side rollback in the dashboard

A stored procedure that wraps its own BEGIN TRY / BEGIN CATCH rolls back internally and re-throws, which surfaces as a CaeriusNetSqlException. The corresponding stored procedure span is tagged ActivityStatusCode.Error. This is expected behavior, not a CaeriusNet bug.


Next: API reference - full surface of all public types and methods.

Released under the MIT License.