JoshMakeStuff.Aspire.OpenLdap 0.8.0-preview.1

This is a prerelease version of JoshMakeStuff.Aspire.OpenLdap.
dotnet add package JoshMakeStuff.Aspire.OpenLdap --version 0.8.0-preview.1
                    
NuGet\Install-Package JoshMakeStuff.Aspire.OpenLdap -Version 0.8.0-preview.1
                    
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="JoshMakeStuff.Aspire.OpenLdap" Version="0.8.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JoshMakeStuff.Aspire.OpenLdap" Version="0.8.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="JoshMakeStuff.Aspire.OpenLdap" />
                    
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 JoshMakeStuff.Aspire.OpenLdap --version 0.8.0-preview.1
                    
#r "nuget: JoshMakeStuff.Aspire.OpenLdap, 0.8.0-preview.1"
                    
#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 JoshMakeStuff.Aspire.OpenLdap@0.8.0-preview.1
                    
#: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=JoshMakeStuff.Aspire.OpenLdap&version=0.8.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=JoshMakeStuff.Aspire.OpenLdap&version=0.8.0-preview.1&prerelease
                    
Install as a Cake Tool

JoshMakeStuff.Aspire.OpenLdap

A .NET Aspire client integration for OpenLDAP. Registers an LdapConnection (from System.DirectoryServices.Protocols) in DI, wired to the connection string published by the JoshMakeStuff.Aspire.Hosting.OpenLdap resource.

The package ID carries the JoshMakeStuff. prefix because Aspire.* is reserved on nuget.org. The API namespace is still Microsoft.Extensions.Hosting, so AddOpenLdapClient(...) resolves without any extra using.

Install

dotnet add package JoshMakeStuff.Aspire.OpenLdap

Install this package in the service project that talks to LDAP (not the AppHost).

Usage

var builder = WebApplication.CreateBuilder(args);

builder.AddOpenLdapClient("ldap");

var app = builder.Build();

app.MapGet("/whoami", (LdapConnection conn) =>
{
    var who = (ExtendedResponse)conn.SendRequest(new ExtendedRequest("1.3.6.1.4.1.4203.1.11.3"));
    return Results.Text(Encoding.UTF8.GetString(who.ResponseValue ?? []));
});

app.Run();

The connectionName ("ldap" above) must match the resource name passed to AddOpenLdap(...) in your AppHost.

What gets registered

  • OpenLdapClientFactory (singleton) — parses the connection string, applies settings, and creates LdapConnection instances (CreateConnection()) or instrumented OpenLdapClient instances (CreateClient()). A service that outlives a single LDAP operation takes the factory and creates a client per operation, rather than capturing the transient OpenLdapClient — neither it nor the connection it wraps is thread-safe.
  • LdapConnection (transient) — resolved from the factory.
  • OpenLdapClient (transient) — an instrumented wrapper over LdapConnection; use it to get OpenTelemetry traces/metrics (see Telemetry).
  • A health check named openldap_{connectionName} that performs a root-DSE search (disable with settings.DisableHealthChecks = true).

Multiple directories (keyed)

To connect to more than one OpenLDAP resource, register each with AddKeyedOpenLdapClient — the connection name doubles as the DI service key:

builder.AddKeyedOpenLdapClient("corp");
builder.AddKeyedOpenLdapClient("partners");

app.MapGet("/corp", ([FromKeyedServices("corp")] LdapConnection conn) => /* ... */);

This registers OpenLdapClientFactory and LdapConnection as keyed services under the name, plus a health check named openldap_{name}.

Configuration

Bind from Aspire:OpenLdap in configuration, or pass a callback:

builder.AddOpenLdapClient("ldap", settings =>
{
    settings.DisableHealthChecks = false;
    // settings.ConnectionString — overrides the resolved connection string
});

The connection string is normally provided automatically by Aspire via ConnectionStrings:{connectionName}.

Reading and writing connection strings

OpenLdapConnectionStringBuilder handles both directions, so the format and its quoting rules live in one place instead of being re-implemented by every consumer:

// Read
var settings = OpenLdapConnectionStringBuilder.Parse(connectionString);
var baseDn = settings.BaseDn;

// Write — for a host that synthesizes its own connection string from env vars or config
var connectionString = new OpenLdapConnectionStringBuilder
{
    Endpoint = new Uri($"ldap://{host}:{port}"),
    BaseDn = baseDn,
    BindDn = bindDn,
    BindPassword = password,
    CaCertFile = caPath,      // optional; omitted from the output when null or empty
}.Build();

Build() quotes any value containing ; or ", carrying leading/trailing whitespace, or empty — so arbitrary passwords and DNs survive the round trip — and rejects the same endpoints Parse rejects, meaning it cannot emit a string the parser would refuse.

It is a named method rather than a ToString() override on purpose: the object holds BindPassword, and an override would turn any log line or string interpolation mentioning the instance into a credential leak.

LDAPS trust

When the connection string carries CaCertFile=... (the hosting integration appends it under WithRequiredTls()), connections trust that CA. The mechanism is platform-specific:

  • Windows — a managed callback validates the chain against the CA and that the certificate names the endpoint host. settings.DisableTlsHostnameValidation relaxes the hostname part only.
  • Linux — the CA is trusted natively by libldap via an OpenSSL hash-named certificate directory (staged automatically under the temp folder). libldap always validates the peer, so DisableTlsHostnameValidation is rejected there. DNS-name endpoints are dialed by IP literal (libldap checks a name dial against the peer's reverse-DNS name, which loopback resolves to the machine hostname), so the server certificate must carry the endpoint's IP SAN — e.g. 127.0.0.1 for local development; the hosting integration's generated certificates do.
  • macOS — Apple's LDAP.framework cannot trust a custom CA from managed code; connection creation fails with guidance. Set settings.TrustConnectionStringCaCertificate = false and add the CA to the system trust store out of band instead.

Telemetry

Operations issued through OpenLdapClient (resolve it from DI and call Send / SendAsync instead of using the raw LdapConnection) emit OpenTelemetry traces and metrics under the source/meter name Aspire.OpenLdap. AddOpenLdapClient registers the source and meter with the app's OpenTelemetry pipeline automatically, so they flow to whatever exporter you've configured (e.g. via Aspire's AddServiceDefaults).

builder.AddOpenLdapClient("ldap");

app.MapGet("/users", (OpenLdapClient ldap) =>
{
    var resp = (SearchResponse)ldap.Send(
        new SearchRequest("ou=users,dc=example,dc=org", "(objectClass=person)", SearchScope.Subtree, null));
    return Results.Ok(resp.Entries.Count);
});
  • Traces — one span per operation named LDAP <op> (e.g. LDAP search), kind Client, with attributes db.system.name=openldap, db.operation.name, server.address, server.port, db.response.status_code / db.ldap.result_code, and for searches db.ldap.scope, db.ldap.entries_returned, db.ldap.controls (control OIDs), db.ldap.paged.
  • Metrics — a histogram db.client.operation.duration (seconds) tagged by operation, server, and result/error.
  • Privacy — search filters, DNs, entry attributes, control values, and paging-cookie bytes are never recorded.
  • Disabling — set settings.DisableTracing and/or settings.DisableMetrics (or Aspire:OpenLdap:DisableTracing in configuration).
  • Note — only OpenLdapClient is instrumented; the raw LdapConnection and the OpenLdapClient.Connection escape hatch are not.

For a runnable end-to-end demo (AppHost + Web API + dashboard), see the examples/ folder.

AI coding agents

This package ships an agent-facing API reference covering both the client and hosting integrations. After restore it is at ~/.nuget/packages/joshmakestuff.aspire.openldap/<version>/AGENTS.md (also skills/SKILL.md). Add a pointer to it from your repo's AGENTS.md, or copy it to .agents/skills/aspire-openldap/SKILL.md, so coding agents find it.

Requirements on Linux

LdapConnection comes from System.DirectoryServices.Protocols, which on Linux P/Invokes the native OpenLDAP client library. (On Windows it uses the built-in wldap32.dll and needs nothing extra.) The runtime hardcodes a load of libldap-2.5.so.0 — still true on .NET 10 (dotnet/runtime#123676) — but modern distros (Ubuntu 24.04+, Fedora, Alpine 3.20+) ship the upstream soname libldap.so.2 instead.

AddOpenLdapClient / AddKeyedOpenLdapClient handle this automatically: they register a resolver that probes the sonames distros actually ship (libldap-2.5.so.0, libldap.so.2, libldap-2.6.so.0, libldap-2.4.so.2). You only need the OpenLDAP client library installed — no symlinks:

sudo apt-get install -y libldap2      # Ubuntu 24.04+ (22.04: libldap-2.5-0)
sudo dnf install -y openldap          # Fedora
sudo apk add libldap                  # Alpine

The automatic resolution only applies when you register through the Add* methods. If you use System.DirectoryServices.Protocols directly elsewhere in your app before calling them, symlink the shipped soname to the 2.5 name as a fallback (confirm what you have with ldconfig -p | grep -E 'libldap|liblber'):

# Path is /usr/lib/x86_64-linux-gnu on Debian/Ubuntu, /usr/lib64 on Fedora.
sudo ln -sf .../libldap.so.2 .../libldap-2.5.so.0
sudo ldconfig

Without either, you'll see Unable to load shared library 'libldap-2.5.so.0' at the first LDAP call.

Product 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. 
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
0.8.0-preview.1 73 9/8/2026
0.7.0-preview.1 95 8/8/2026
0.6.0-preview.1 73 8/7/2026
0.5.0-preview.1 118 7/18/2026
0.4.0-preview.1 70 7/17/2026
0.3.0-preview.1 81 7/14/2026
0.2.0-preview.1 72 6/28/2026
0.1.0-preview.1 70 6/27/2026