# Dependency Injection This document describes the unified `Microsoft.Extensions.DependencyInjection` surface for the OPC UA .NET Standard libraries. The surface is rooted in a single `services.AddOpcUa()` call that returns an `IOpcUaBuilder` on which every feature library hangs its own fluent `.AddXxx(...)` extension. The dependency injection surface is consistent across: - The OPC UA Core stack (`src/Opc.Ua.Core`) - Application configuration (`src/Opc.Ua.Configuration`) - The client (`src/Opc.Ua.Client`) - The complex types client (`src/Opc.Ua.Client.ComplexTypes`) - Alarms and conditions client (`src/Opc.Ua.Client.Alarms`) - The server (`src/Opc.Ua.Server`) - The GDS client (`src/Opc.Ua.Gds.Client.Common`) - The GDS server (`src/Opc.Ua.Gds.Server.Common`) - The LDS server (`src/Opc.Ua.Lds.Server`) - The WoT Connectivity server (`src/Opc.Ua.WotCon.Server`) - The WoT Connectivity client (`src/Opc.Ua.WotCon.Client`) - The AAS V3 server (`src/Opc.Ua.Aas.Server`) - The AAS V3 client (`src/Opc.Ua.Aas.Client`) - The PubSub stack (`src/Opc.Ua.PubSub`, `src/Opc.Ua.PubSub.Udp`, `src/Opc.Ua.PubSub.Mqtt`, `src/Opc.Ua.PubSub.Server`) — see [`PubSub.md`](PubSub.md) for the full library reference. The non-dependency-injection public constructors and factories of every library (`new ApplicationInstance(telemetry)`, `new StandardServer(telemetry)`, `new LdsServer(telemetry)`, `new ManagedSession(...)` etc.) remain unchanged. Use dependency injection when you want the .NET Generic Host to own application lifetime, logging, and configuration; use the manual constructors when you need finer control. ## Quick reference | Feature library | Method on `IOpcUaBuilder` | Returns | Hosted? | Section | |--------------------------------|------------------------------------------|--------------------------|---------|--------------------------| | `Opc.Ua.Core` (root) | `services.AddOpcUa()` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Configuration` | `builder.ConfigureApplication(opt => …)` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Configuration` | `builder.AddApplicationInstance()` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Core` (crypto) | `builder.AddCryptoProvider(crypto => …)` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Security.Pkcs11` | `builder.AddPkcs11CertificateStore(…)` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Client` | `builder.AddClient(opt => …)` | `IOpcUaClientBuilder` | — | `OpcUa:Client` | | `Opc.Ua.Client.ComplexTypes` | `builder.AddComplexTypes()` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Client.Alarms` (within `Opc.Ua.Client`) | `builder.AddAlarms()` | `IOpcUaBuilder` | — | — | | `Opc.Ua.Server` | `builder.AddServer(opt => …)` | `IOpcUaServerBuilder` | yes | `OpcUa:Server` | | `Opc.Ua.Server` (node manager)| `builder.AddNodeManager()` | `IOpcUaServerBuilder` | — | — | | `Opc.Ua.Server` (runtime NodeSet) | `builder.AddRuntimeNodeSet(…)` | `IOpcUaServerBuilder` | — | — | | `Opc.Ua.Server` (live NodeManagers) | resolve `INodeManagerLifecycle` | `INodeManagerLifecycle` | yes | — | | `Opc.Ua.Positioning.Server` | `serverBuilder.AddPositioningServer()` / `AddPositioningFor()` | `IPositioningServerBuilder` | yes (via `AddServer`) | — | | `Opc.Ua.Positioning.Client` | `clientBuilder.AddPositioningClient()` | `IOpcUaClientBuilder` | — | — | | `Opc.Ua.Robotics.Server` | `serverBuilder.AddRobotics(opt => …)` | `IOpcUaServerBuilder` | yes (via `AddServer`) | — | | `Opc.Ua.Robotics.Server` (model) | `serverBuilder.AddRoboticsModel()` | `IOpcUaServerBuilder` | — | — | | `Opc.Ua.Robotics.Server` (build) | `serverBuilder.ConfigureRobotics(…)` / `ConfigureRoboticsFor(…)` | `IOpcUaServerBuilder` | — | — | | `Opc.Ua.Robotics.Client` | `clientBuilder.AddRoboticsClient()` | `IOpcUaClientBuilder` | — | — | | `Opc.Ua.Gds.Client.Common` | `builder.AddGdsClient(opt => …)` | `IGdsClientBuilder` | — | `OpcUa:Gds:Client` | | `Opc.Ua.Gds.Server.Common` | `builder.AddGdsServer(opt => …)` | `IGdsServerBuilder` | yes | `OpcUa:Gds:Server` | | `Opc.Ua.Lds.Server` | `builder.AddLdsServer(opt => …)` | `ILdsServerBuilder` | yes | `OpcUa:Lds` | | `Opc.Ua.WotCon.Server` | `builder.AddWotConServer(opt => …)` | `IWotConServerBuilder` | yes (via `AddServer`) | `OpcUa:WotCon:Server` | | `Opc.Ua.WotCon.Client` | `builder.AddWotConClient(opt => …)` | `IOpcUaBuilder` | — | `OpcUa:WotCon:Client` | | `Opc.Ua.Aas.Server` | `builder.AddAasV3Server(opt => …)` | `IAasServerBuilder` | yes (via `AddServer`) | `OpcUa:Aas:Server` | | `Opc.Ua.Aas.Client` | `builder.AddAasV3Client(opt => …)` / `AddAasV3RegistryClient(opt => …)` | `IOpcUaBuilder` | — | `OpcUa:Aas:Client` | | `Opc.Ua.PubSub` | `builder.AddPubSub(opt => …)` | `IPubSubBuilder` | yes | `OpcUa:PubSub` | | `Opc.Ua.PubSub` (publish-only) | `builder.AddPubSubPublisher(opt => …)` | `IPubSubBuilder` | yes | `OpcUa:PubSub` | | `Opc.Ua.PubSub` (subscribe-only) | `builder.AddPubSubSubscriber(opt => …)`| `IPubSubBuilder` | yes | `OpcUa:PubSub` | | `Opc.Ua.PubSub` (SKS client) | `builder.AddPubSubSecurityKeyServiceClient(...)` | `IPubSubBuilder` | — | `OpcUa:PubSub:Sks:Client`| | `Opc.Ua.PubSub` (SKS server) | `builder.AddPubSubSecurityKeyServiceServer(...)` | `IPubSubBuilder` | yes | `OpcUa:PubSub:Sks:Server`| | `Opc.Ua.PubSub.Udp` | `pubSub.AddUdpTransport()` | `IPubSubBuilder` | — | — | | `Opc.Ua.PubSub.Mqtt` | `pubSub.AddMqttTransport()` | `IPubSubBuilder` | — | — | | `Opc.Ua.PubSub.Server` | `serverBuilder.AddPubSubAddressSpace(...)` | `IPubSubServerBuilder` | yes | `OpcUa:PubSub:AddressSpace` | | `Opc.Ua.Redundancy.Server` | `serverBuilder.UseDistributedAddressSpace(...)` / `UseReplicatedAddressSpace(...)` / `UseActiveActiveRedundancy(...)` / `UsePeerDiscovery(...)` / `AddServerRedundancy(...)` | `IOpcUaServerBuilder` | yes (via `AddServer`) | see [HighAvailability.md](HighAvailability.md) | | `Opc.Ua.Redundancy.Kubernetes` | `serverBuilder.UseKubernetesRaftConsensus(...)` | `IOpcUaServerBuilder` | yes (via `AddServer`) | see [Kubernetes.md](Kubernetes.md) | Identity-provider extensions hang off `IOpcUaServerBuilder`, `IOpcUaClientBuilder`, and `IGdsServerBuilder`: | Extension | Builder | Section | |-----------------------------------------------|--------------------------|----------------------------------| | `AddDefaultIdentityAuthenticators(...)` | server, gds | `OpcUa:Server:Identity:Defaults` | | `AddIdentityAuthenticator()` | server, gds | — | | `AddIdentityAugmenter()` | server, gds | — | | `AddGdsApplicationSelfAdminProvider()` | gds | — | | `AddJwtIssuer(...)` | server, gds | `OpcUa:Server:Identity:Issuers[]`| | `WithAuthorizationService(...)` / `()` | gds | — | | `WithKeyCredentialPush(...)` | server | — | | `ConfigureRoles(...)` | server, gds | `OpcUa:Server:Roles` | | `AddIdentityProvider(...)` / `()` | client | `OpcUa:Client:Identity` | | `AddAccessTokenProvider(...)` / `()` | client | — | See the [Identity (server)](#identity-server), [Identity (client)](#identity-client), and [Identity (GDS server)](#identity-gds-server) sections below and the full [Identity Providers](IdentityProviders.md) guide. Server features marked **Hosted? = yes** register an `IHostedService` so the .NET Generic Host (`Host.CreateApplicationBuilder(args)`) owns their lifetime, certificate setup, and Ctrl+C / SIGTERM handling. ## Root: `services.AddOpcUa()` `services.AddOpcUa()` is the only entry point. It does three things: 1. Registers `ITelemetryContext` as a `ServiceProviderTelemetryContext` singleton (via `TryAddSingleton`, so any prior user registration wins). 2. Registers the default `IBufferManagerFactory` and `BufferManagerFactoryOptions` singletons (also via `TryAddSingleton`). 3. Returns an `IOpcUaBuilder` whose `.Services` property exposes the underlying `IServiceCollection` for advanced scenarios. ```csharp using Microsoft.Extensions.DependencyInjection; services.AddOpcUa(); ``` `AddOpcUa` is idempotent. The returned `IOpcUaBuilder` derives from the older `IDependencyInjectionBuilder` so existing `builder.AddLogging()` / `builder.AddMetrics()` extensions still compile unchanged. New `AddLogging(this IOpcUaBuilder)` / `AddMetrics(this IOpcUaBuilder)` overloads return `IOpcUaBuilder` for fluent chaining into feature methods: ```csharp services.AddOpcUa() .AddLogging(b => b.AddConsole()) .AddMetrics() .AddServer(o => /* … */) .AddNodeManager(); ``` ### Buffer managers Transport listeners and channels resolve `IBufferManagerFactory` from dependency injection. The default factory selects `FastBufferManager` in Release builds, `CookieBufferManager` in Debug builds, and `TracingBufferManager` when the stack is compiled with `TRACK_MEMORY`. Register options before `AddOpcUa()` to select an implementation explicitly or apply a process-wide outstanding-buffer budget: ```csharp services.AddSingleton(new BufferManagerFactoryOptions { ImplementationKind = BufferManagerImplementationKind.Fast, MaxOutstandingBytesPerProcess = 256L * 1024 * 1024 }); services.AddOpcUa() .AddOpcTcpTransport() .AddHttpsTransport(); ``` When `MaxOutstandingBytesPerProcess` is positive, the singleton factory wraps every manager it creates with `LimitingBufferManager` and shares one `BufferManagerMemoryLimiter` across them. A synchronous rent blocks without holding a manager lock until another buffer is returned. A single rent whose conservative expected size exceeds the budget fails immediately instead of waiting forever. Applications can replace the complete policy by registering an `IBufferManagerFactory` before `AddOpcUa()`: ```csharp services.AddSingleton(); services.AddOpcUa(); ``` The existing `new BufferManager(name, maxBufferSize, telemetry)` path remains available for direct creation. `BufferManager` is a compatibility facade over `IBufferManager`; `FastBufferManager`, `CookieBufferManager`, `TracingBufferManager`, and `LimitingBufferManager` can also be created directly for advanced scenarios. ## Shared application configuration `ConfigureApplication(...)` defines the application identity, PKI root, and common certificate-validation settings once. `AddClient(...)`, `AddServer(...)`, or both contribute their feature-specific sections to one `ApplicationConfiguration`; the provider calls `ApplicationConfigurationBuilder.CreateAsync`, validates the completed configuration, and ensures the application instance certificate exists before a client connects or a hosted server starts. Use `ConfigureApplication(...)` for combined client/server hosts, or when several `AddClient(...)`/`AddServer(...)` registrations must share one application identity and certificate lifecycle. A client-only host can instead set the same identity fields directly on `OpcUaClientOptions` inside `AddClient(...)` (see [Client feature](#client-feature)) without a separate `ConfigureApplication(...)` call. ```csharp IOpcUaBuilder opcUa = services.AddOpcUa() .ConfigureApplication(options => { options.ApplicationName = "MyApplication"; options.ApplicationUri = "urn:localhost:MyApplication"; options.ProductUri = "uri:example.com:MyApplication"; options.SubjectName = "CN=MyApplication, O=Example, DC=localhost"; options.PkiRoot = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Example", "MyApplication", "pki"); options.AutoAcceptUntrustedCertificates = false; options.RejectSHA1SignedCertificates = true; options.MinimumCertificateKeySize = 2048; // Code-only advanced settings, applied after the values above. options.ConfigureSecurity = security => security .SetMaxRejectedCertificates(20) .SetRejectUnknownRevocationStatus(true) .SetUseValidatedCertificates(false) .SetSendCertificateChain(true); }); opcUa.AddClient(options => { options.Session = new ManagedSessionOptions { SessionName = "MyClient" }; }); opcUa.AddServer(options => { options.EndpointUrls.Add("opc.tcp://localhost:4840/MyApplication"); }); ``` When both features are registered, the shared configuration has `ApplicationType.ClientAndServer` and one application-certificate lifecycle. An explicitly supplied `OpcUaClientOptions.Configuration` still wins for that client registration. ### Application identity and certificate defaults | Setting | Behavior when omitted | |---------|-----------------------| | `ApplicationName` | Required after client/server feature defaults are applied. The server feature defaults to `OpcUaServer`; client-only applications should set it explicitly. | | `ApplicationUri` | Generated from the host name and application name during validation. Set a stable URI for deployed applications. | | `ProductUri` | Uses the contributing feature value. Set a stable product URI for deployed applications. | | `SubjectName` | `CN={ApplicationName}, O=OPC Foundation, DC=localhost`; `DC=localhost` is replaced with the host name. | | `PkiRoot` | A per-application `OPC Foundation/{ApplicationName}/pki` directory below the process temporary directory. Configure a persistent, access-controlled location in production. | | Application certificates and stores | Directory-backed application, trusted peer/issuer, HTTPS, user, and rejected stores are created below `PkiRoot`; default RSA and supported ECC application-certificate identifiers are selected. | The security builder starts with secure defaults: unknown certificates are not auto-accepted, the application certificate is not copied into a shared trusted store, SHA-1 certificates and unknown revocation status are rejected, nonce validation errors are not suppressed, certificate chains are sent, the minimum RSA key size is 2048, and at most five rejected certificates are retained. Validated-certificate caching is off unless enabled explicitly. ### Advanced application certificate validation `OpcUaApplicationOptions.ConfigureSecurity` is an `Action?`. It is invoked after the default application certificates and stores and after the first-class `AutoAcceptUntrustedCertificates`, `RejectSHA1SignedCertificates`, and `MinimumCertificateKeySize` values. It is the final security customization before `CreateAsync`, so it can override those first-class values. The callback is code-only and is not bound from `IConfiguration`; exceptions are not swallowed and fail application-configuration provider creation. Prefer the first-class properties for their common settings and use `ConfigureSecurity` only for advanced certificate/store behavior: | Method | Purpose and secure default | |--------|----------------------------| | `SetApplicationCertificates(...)` | Replaces the generated application-certificate identifiers. Prefer `SubjectName` and `PkiRoot` for the normal generated layout. | | `SetMaxRejectedCertificates(...)` | Sets rejected-certificate retention; default `5`, `0` keeps all, and a negative value keeps no history. | | `SetAutoAcceptUntrustedCertificates(...)` | Accepts otherwise-valid unknown peer certificates; default `false`. Prefer `AutoAcceptUntrustedCertificates`. Use `true` only in an isolated lab. | | `SetAddAppCertToTrustedStore(...)` | Adds a newly created application certificate to a shared trusted store; default `false`. | | `SetRejectSHA1SignedCertificates(...)` | Rejects SHA-1-signed certificates; default `true`. Prefer `RejectSHA1SignedCertificates`. | | `SetRejectUnknownRevocationStatus(...)` | Rejects chains when CA revocation status cannot be determined; default `true`. | | `SetUseValidatedCertificates(...)` | Reuses previously validated certificates without repeating the full validation path; default `false`. Leave disabled unless the reduced revalidation is explicitly acceptable for the deployment. | | `SetSuppressNonceValidationErrors(...)` | Suppresses zero/weak nonce errors; default `false`. Enabling it weakens user-token protection and is only for unavoidable legacy interoperability. | | `SetSendCertificateChain(...)` | Sends the chain with a CA-signed application certificate; default `true`. | | `SetMinimumCertificateKeySize(...)` | Sets the minimum accepted RSA key size; default `2048`. Prefer `MinimumCertificateKeySize`. | | `AddCertificatePasswordProvider(...)` | Supplies an `ICertificatePasswordProvider` for protected private keys; no provider is registered by default. | See [Certificates](Certificates.md) for trust-store deployment and validation, and [Certificate Manager](CertificateManager.md) for injectable certificate lifecycle management. Discovery servers expose the same transport and reverse-connect shortcuts as the regular server builder: ```csharp services.AddOpcUa() .AddInMemoryGdsServer(o => o.EndpointUrls.Add("opc.tcp://localhost:58810/GDS")) .AddOpcTcpTransport() .AddHttpsTransport() .AddReverseConnect(o => o.Clients.Add(new ServerReverseConnectClientOptions { EndpointUrl = "opc.tcp://client.example.com:4841" })); services.AddOpcUa() .AddLdsServer(o => o.EndpointUrls.Add("opc.tcp://localhost:4840/LDS")) .AddRegistrationStore() .AddMulticastDiscovery(); services.AddOpcUa() .AddWotConnectivityServer( server => server.EndpointUrls.Add("opc.tcp://localhost:4840/WoT"), wot => wot.AssetNamespaceUri = WotConnectivityServerOptions.DefaultAssetNamespaceUri); ``` `AddHttpsTransport()` is supplied by `OPCFoundation.NetStandard.Opc.Ua.Bindings.Https`. Add that package directly when enabling HTTPS, WSS, Kestrel-hosted OPC TCP, or the REST binding; the GDS server package does not reference it transitively. ## Options binding Every feature `.AddXxx(...)` has three overloads: ```csharp // 1. Action — AOT-safe, recommended for all consumers. builder.AddServer(o => { o.ApplicationName = "MyServer"; /* … */ }); // 2. IConfiguration — bind from a configuration root's default section. builder.AddServer(builder.Configuration); // 3. IConfigurationSection — bind from an explicit section. builder.AddServer(builder.Configuration.GetSection("MyApp:Server")); ``` The default section names are: | Feature | Section | |--------------------------------|--------------------------| | Server | `OpcUa:Server` | | Client | `OpcUa:Client` | | GDS Client | `OpcUa:Gds:Client` | | GDS Server | `OpcUa:Gds:Server` | | LDS Server | `OpcUa:Lds` | | WoT Connectivity Server | `OpcUa:WotCon:Server` | | WoT Connectivity Client | `OpcUa:WotCon:Client` | | AAS V3 Server | `OpcUa:Aas:Server` | | AAS V3 Client | `OpcUa:Aas:Client` | The `IConfiguration` / `IConfigurationSection` overloads are **AOT-safe** on every library. Each dependency-injection-emitting library opts into the .NET 8+ [Configuration Binding Source Generator](https://learn.microsoft.com/dotnet/core/extensions/configuration-generator) (`true`), which replaces the reflection-based binder with statically-generated [C# 12 interceptors](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-12#interceptors). The `tests/Opc.Ua.Aot.Tests` project verifies that `dotnet publish` under `PublishAot=true` produces zero `IL2026` / `IL3050` warnings from the dependency injection surface. ## Server feature `builder.AddServer(o => …)` registers an OPC UA `StandardServer` as an `IHostedService` via a private `OpcUaServerHostedService`. Endpoints, PKI root, security policies, and the application instance certificate are all set up on host startup. ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Logging.AddConsole(); builder.Services .AddOpcUa() .AddServer(o => { o.ApplicationName = "MyServer"; o.ApplicationUri = "urn:localhost:MyOrg:MyServer"; o.ProductUri = "uri:myorg:myserver"; o.AutoAcceptUntrustedCertificates = false; o.EndpointUrls.Add("opc.tcp://localhost:51210/MyServer"); }) .AddNodeManager() // IAsyncNodeManagerFactory .AddSyncNodeManager(); // INodeManagerFactory await builder.Build().RunAsync(); ``` `.AddNodeManager()` and `.AddSyncNodeManager()` register the factory under an `OpcUaServerNodeManagerRegistration` wrapper that is **scoped to the regular server feature**. Node managers registered this way are **not** visible to the GDS / LDS hosted services running in the same container. See *Combined hosts* below. `.AddServer(...)` throws `InvalidOperationException` on a second call: at most one regular server may be registered per service collection. The regular hosted server also registers `INodeManagerLifecycle` as a singleton forwarding provider. After the server reaches `Running`, application services can inject it to add, reload, or remove lifecycle-owned NodeManagers. Calls made before startup or after shutdown fail explicitly. ```csharp public sealed class RuntimeModelService(INodeManagerLifecycle lifecycle) { public ValueTask AddAsync(CancellationToken ct) { return lifecycle.AddRuntimeNodeSetAsync( new RuntimeNodeSetOptions { Sources = [RuntimeNodeSetSource.FromFile("Models/Line.NodeSet2.xml")] }, callerContext: null, ct); } } ``` Applications that construct `StandardServer` directly use `server.NodeManagerLifecycle` instead. Both paths use the same lifecycle implementation and generation-aware registration handles. Advanced server services can be supplied through the same fluent builder: ```csharp builder.Services .AddOpcUa() .AddServer(o => /* … */) // optional StandardServer subclass .AddSessionManager() .AddSubscriptionManager() .AddDurableSubscriptions(subscriptionStore, monitoredItemQueueFactory) .AddHistorian(historianProvider) .AddFileSystem(fileSystemProvider) .AddSecretStore(secretStore) .AddCertificateManager(certificateManager) .AddAliasNameStore(aliasNameStore); ``` If no identity authenticator is configured, the regular hosted server adds an anonymous authenticator matching its default anonymous user-token policy. If a non-anonymous user-token policy is configured without a corresponding authenticator, startup logs a warning. ### Migrating with an existing configuration XML file Applications that already own a classic OPC UA application configuration file (`*.Config.xml`, previously loaded through `ApplicationInstance.LoadApplicationConfigurationAsync`) can pass that file to `AddServer` directly and keep every setting in it — base addresses, security policies, certificate stores, transport quotas, operation limits, user-token policies — while adopting the hosted dependency-injection surface: ```csharp builder.Services .AddOpcUa() .AddServer("MyServer.Config.xml") .AddNodeManager(); ``` The same file can be referenced from configuration instead (`OpcUa:Server:ConfigurationFile`), and a custom `StandardServer` subclass works too: ```json { "OpcUa": { "Server": { "ConfigurationFile": "MyServer.Config.xml" } } } ``` ```csharp builder.Services.AddOpcUa().AddServer("MyServer.Config.xml"); ``` On this path the file is authoritative: the `OpcUaServerOptions` knobs that feed the configuration builder (`ApplicationName`, `EndpointUrls`, `PkiRoot`, policy toggles, transport quotas, `ConfigureBuilder`, ...) are not applied, and the file also takes precedence over a shared application registered with `ConfigureApplication(...)`. Options that act on the hosted server itself (`Identity`, `ConfigureRateLimits`) and all fluent registrations (`AddNodeManager`, `ConfigureRoles`, `AddDefaultIdentityAuthenticators`, ...) keep working. To override individual file settings from code, pass the optional callback, which runs after the file is loaded and validated but before certificates are checked and the server starts: ```csharp builder.Services .AddOpcUa() .AddServer("MyServer.Config.xml", configuration => { configuration.ServerConfiguration.MaxSessionCount = 25; }); ``` A configuration document that is not a file on disk — an embedded resource, a document fetched from a store — can be supplied as a `Stream` instead (`OpcUaServerOptions.ConfigurationStream`; mutually exclusive with `ConfigurationFile`). The stream must remain open until the host starts the server; it is read once and disposed by the hosted service during startup, so do not wrap it in a `using` yourself: ```csharp Stream stream = typeof(Program).Assembly .GetManifestResourceStream("MyApp.MyServer.Config.xml")!; builder.Services .AddOpcUa() .AddServer(stream) // read + disposed when the host starts the server .AddNodeManager(); ``` The certificate manager and certificate password provider registered in dependency injection are honored the same way as on the options path, and the unmatched user-token-policy startup warning is derived from the policies advertised by the file. The client offers the same migration path — see [Client: using an existing configuration XML file](#client-using-an-existing-configuration-xml-file). ### Server security and resource controls The hosted server is secure by default: sign-and-encrypt policies are included, `SecurityPolicy#None` is excluded, SHA-1 certificates are rejected, the minimum RSA key size is 2048, and unknown certificates are not auto-accepted. Review these controls explicitly for every deployment: - Keep `IncludeSignAndEncryptPolicies = true`. Set `IncludeUnsecurePolicyNone = true` only for an isolated lab endpoint with no sensitive data or credentials. - Enable `IncludeEccPolicies` only when the deployment certificates and clients support the advertised ECC policies. - Configure `UserTokenPolicies` together with matching authenticators. An empty list advertises `Anonymous`; adding a token policy alone does not authenticate it. See [Identity Providers](IdentityProviders.md) and [Role-Based User Management](RoleBasedUserManagement.md). - Keep certificate auto-accept disabled and provision trust lists. For advanced validation, use the shared `ConfigureApplication(...).ConfigureSecurity` callback described above. - Bound transport and service work for the deployment. `MaxByteStringLength`, `MaxArrayLength`, `MaxMessageSize`, and `OperationTimeoutMs` control transport quotas; `OperationLimits` bounds nodes processed by individual services. - Set deployment-specific channel, session, and failed-authentication ceilings through `ConfigureBuilder` to reduce resource-exhaustion and credential-guessing exposure. `OpcUaServerOptions.ConfigureBuilder` receives `IApplicationConfigurationBuilderServerSelected` after the standard transport quotas, policies, user-token policies, and server options are applied, but **before** application certificates and security stores are added. Use it for server policy and server-runtime options. In contrast, `OpcUaApplicationOptions.ConfigureSecurity` runs **after** certificates, stores, and first-class certificate-validation values are applied. Use it for post-security certificate and validation options. ```csharp services.AddOpcUa() .ConfigureApplication(application => { application.ApplicationName = "MyServer"; application.AutoAcceptUntrustedCertificates = false; application.RejectSHA1SignedCertificates = true; application.MinimumCertificateKeySize = 2048; application.ConfigureSecurity = security => security .SetRejectUnknownRevocationStatus(true); }) .AddServer(server => { server.IncludeSignAndEncryptPolicies = true; server.IncludeUnsecurePolicyNone = false; server.IncludeEccPolicies = true; server.ConfigureBuilder = configuration => configuration .SetMaxFailedAuthenticationAttempts(5) .SetMaxSessionCount(100) .SetMaxChannelCount(200) .SetAuditingEnabled(true) .SetHttpsMutualTls(true); server.ConfigureRateLimits = limits => { limits.ConnectionsPerSecond = 200; limits.ConnectionBurst = 400; limits.MaxConcurrentSessionEstablishment = 64; }; }); ``` `SetAuditingEnabled(true)` enables OPC UA audit-event reporting. `SetHttpsMutualTls(true)` requests and validates a client certificate when one is supplied on HTTPS endpoints; it does not by itself require every client to present one. Enforce certificate-only Web API access through the corresponding authentication and authorization setup. Server admission rate limiting is enabled by default with conservative connection and concurrent session-establishment limits. Tune it with `ConfigureRateLimits`, or register a custom `IServerRateLimiterProvider`; do not disable it without an equivalent upstream control. See [Rate Limiting](RateLimiting.md) rather than duplicating the full algorithm and deployment guidance here. ### First-class server options In addition to the basic application-identity / endpoint / PKI knobs covered above, `OpcUaServerOptions` exposes the following first-class properties (bindable from `IConfiguration` or set via the `Action` overload). Certificate options added by `AddSecurityConfiguration` are instead reached through `OpcUaApplicationOptions.ConfigureSecurity`. | Property | Underlying builder call | Purpose | |----------|-------------------------|---------| | `IncludeSignAndEncryptPolicies` | `AddSignAndEncryptPolicies()` | Add the standard sign-and-encrypt policies. On by default. | | `IncludeUnsecurePolicyNone` | `AddUnsecurePolicyNone()` | Advertise an unsecured endpoint. Off by default; lab use only. | | `IncludeEccPolicies` | `AddEccSignAndEncryptPolicies()` | Add ECC sign-and-encrypt security policies. Off by default. | | `UserTokenPolicies` | `AddUserTokenPolicy(UserTokenType)` | List of user-token policies advertised on every endpoint. Defaults to `Anonymous` when empty. | | `MaxByteStringLength` | `SetMaxByteStringLength(int)` | Transport byte-string quota; defaults to 4 MiB. | | `MaxArrayLength` | `SetMaxArrayLength(int)` | Transport array-element quota; defaults to 1 Mi elements. | | `MaxMessageSize` | `SetMaxMessageSize(int)` | Transport quota in bytes; `null` keeps the stack default. | | `OperationTimeoutMs` | `SetOperationTimeout(int)` | Transport operation timeout in ms; `null` keeps the stack default. | | `RejectSHA1Certificates` | `SetRejectSHA1SignedCertificates(bool)` | Security hardening — defaults to `true`. | | `MinCertificateKeySize` | `SetMinimumCertificateKeySize(ushort)` | Security hardening — defaults to `2048`. Set to `0` to keep the stack default. | | `RegistrationEndpointUrl` | `SetRegistrationEndpoint(EndpointDescription)` | LDS/GDS endpoint URL the server registers itself with on startup. | | `ReverseConnect` | `SetReverseConnect(ReverseConnectServerConfiguration)` | Server-side reverse-connect clients (see below). | | `OperationLimits` | `SetOperationLimits(OperationLimits)` | Per-service node limits (max nodes per read/write/browse/...). | | `ConfigurationFile` | `LoadApplicationConfigurationAsync(path)` | Load the configuration from an existing OPC UA XML configuration file instead of building it from the options (see [Migrating with an existing configuration XML file](#migrating-with-an-existing-configuration-xml-file)). | | `ConfigurationStream` | `LoadApplicationConfigurationAsync(stream)` | Code-only: load the configuration from a stream (e.g. an embedded resource); read once and disposed at startup. Mutually exclusive with `ConfigurationFile`. | | `ConfigureLoadedConfiguration` | Code-only callback | Override individual settings of the configuration loaded from `ConfigurationFile` / `ConfigurationStream`. | | `ConfigureBuilder` | Code-only callback | Pre-security server-policy and server-option escape hatch, including max failed authentication attempts, sessions, channels, auditing, and HTTPS mutual TLS. | | `ConfigureRateLimits` | Code-only callback | Tunes the default connection and session-establishment admission controls. | ### Server-side reverse connect A server can dial back to clients via reverse-hello using `OpcUaServerOptions.ReverseConnect`. The data binds directly from `OpcUa:Server:ReverseConnect`: ```jsonc { "OpcUa": { "Server": { "ReverseConnect": { "ConnectIntervalMs": 15000, "ConnectTimeoutMs": 30000, "RejectTimeoutMs": 60000, "Clients": [ { "EndpointUrl": "opc.tcp://client.example.com:4841", "Timeout": 30000, "MaxSessionCount": 1, "Enabled": true } ] } } } } ``` Equivalent code-only configuration: ```csharp services.AddOpcUa().AddServer(o => { o.ReverseConnect = new ServerReverseConnectOptions(); o.ReverseConnect.Clients.Add(new ServerReverseConnectClientOptions { EndpointUrl = "opc.tcp://client.example.com:4841", Enabled = true }); }); ``` ### Operation limits ```csharp services.AddOpcUa().AddServer(o => { o.OperationLimits = new OperationLimitsOptions { MaxNodesPerRead = 1000, MaxNodesPerWrite = 1000, MaxNodesPerBrowse = 1000, MaxMonitoredItemsPerCall = 5000 }; }); ``` Bindable from `OpcUa:Server:OperationLimits`. Any value left at zero is treated as "unlimited" by the OPC UA server stack. ### User token policies ```csharp services.AddOpcUa().AddServer(o => { o.UserTokenPolicies.Add(new OpcUaUserTokenPolicy { TokenType = UserTokenType.UserName }); o.UserTokenPolicies.Add(new OpcUaUserTokenPolicy { TokenType = UserTokenType.Certificate }); }); ``` Bindable from `OpcUa:Server:UserTokenPolicies`. When the list is empty the hosted service falls back to a single `Anonymous` policy. ### Identity (server) > Full identity surface is documented in > [Identity Providers](IdentityProviders.md). This section covers only > the dependency injection bindings. `builder.AddServer(...)` exposes identity-related extension methods on `IOpcUaServerBuilder`: | Extension | Purpose | |---|---| | `ConfigureRoles(Action)` / `(IConfiguration)` | Registers `RoleConfigurationOptions` for future role-related tuning. The DTO currently has no configurable members; the extension exists as a stable expansion point. | | `AddIdentityAuthenticator()` | Registers a single custom `IUserTokenAuthenticator` implementation. The hosted service adds it to `IServerInternal.IdentityRegistry` on startup. | | `AddIdentityAugmenter()` | Registers a single custom `IIdentityAugmenter` implementation. The hosted service runs it after accepted authentications. | | `AddDefaultIdentityAuthenticators(Action)` / `(IConfiguration)` | Registers the four in-box authenticators (Anonymous, UserNamePassword, X509, Jwt) with toggles per type plus the JWT audience / clock-skew settings. | | `AddJwtIssuer(Action)` / `(IConfiguration)` | Registers a trusted JWT issuer. Multiple calls coexist; each contributes a `StaticIssuerKeyResolver` and / or `JwksIssuerKeyResolver` keyed by `IssuerUri`. | | `WithKeyCredentialPush(Action?)` | Enables the Part 12 §8 resource-server Push binding and registers an `IKeyCredentialStore` if none is supplied. | Configuration binding under `OpcUa:Server:Identity`: ```json { "OpcUa": { "Server": { "Identity": { "Defaults": { "EnableAnonymous": true, "EnableUserNamePassword": true, "EnableX509": true, "EnableJwt": true, "ExpectedAudience": "urn:my-server", "ClockSkewTolerance": "00:01:00", "UserCertificateTrustList": "Users" }, "Issuers": [ { "IssuerUri": "https://login.microsoftonline.com/{tenant}/v2.0", "JwksUri": "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys", "Algorithms": [ "RS256" ] } ] } } } } ``` The `AddServer(IConfiguration)` overload walks this layout and (in addition to binding `OpcUaServerOptions.Identity`) registers each `Issuers[]` entry through `AddJwtIssuer(...)`, binds the `Roles` sub-section into `RoleConfigurationOptions`, and enables the bridge that creates the four default authenticators from the bound `Identity:Defaults` flags. The `AddServer(Action)` code-only overload does NOT auto-wire any of these — call the fluent extensions explicitly when configuring from code. The `IUserDatabase` / `IUserManagement` services needed by the UserNamePassword authenticator must be registered separately — the hosted service skips that authenticator if neither is in dependency injection. See [Role-Based Security](RoleBasedUserManagement.md) for the role-mapping layer. ### Fluent shortcuts and one-shot presets The server hosting surface exposes granular extension points and one-shot presets: ```csharp services.AddOpcUa() .AddSecureServer(options => { options.ApplicationName = "PlantServer"; options.ApplicationUri = "urn:localhost:PlantServer"; options.ProductUri = "urn:example:PlantServer"; options.EndpointUrls.Add("opc.tcp://localhost:4840/PlantServer"); }) .AddOpcTcpTransport() .AddReverseConnect(options => { options.Clients.Add(new ServerReverseConnectClientOptions { EndpointUrl = "opc.tcp://client.example.com:4841" }); }) .ConfigureOperationLimits(options => options.MaxNodesPerRead = 1000) .ConfigureRoles(options => options.Roles.Add(new RoleDefinitionOptions { Name = BrowseNames.WellKnownRole_Observer, Identities = { new RoleIdentityMappingOptions { CriteriaType = IdentityCriteriaType.UserName, Criteria = "operator" } } })); ``` Use `AddRoleManager(IRoleManager)` or `AddRoleManager()` to replace the default role manager. Custom server types that need session, subscription, or durable-subscription DI hooks must derive from `DependencyInjectionStandardServer`; otherwise startup fails fast instead of ignoring those hooks. Fluent node managers can be registered without a factory class: ```csharp services.AddOpcUa() .AddReferenceServer() .AddNodeManager("urn:example:line", nodes => { nodes.Node("ReferenceServer"); }); ``` `AddHistorianFileStore(provider, path)` combines the historian provider registration with a Part 20 file-system mount for demo and lab servers. ### Positioning Use `AddPositioningServer()` when Positioning owns its node manager. Use `AddPositioningFor()` when an existing companion node manager loads and owns the RSL/GPOS models: ```csharp IPositioningServerBuilder positioning = services .AddOpcUa() .AddServer(options => { /* endpoint and application options */ }) .AddPositioningServer(); positioning .AddGeoLocationProvider() .AddRelativeSpatialLocationProvider() .ConfigurePositioningFor(context => { // Build and register RSL/GPOS instances through context.AddressSpace. return default; }); ``` Client factories compose the generated proxies over the managed session: ```csharp services.AddOpcUa() .AddClient(options => { /* endpoint and application options */ }) .AddPositioningClient(); ``` See [Relative Spatial Location and Global Positioning](Positioning.md). ### Robotics `AddRobotics()` registers the stock `RoboticsNodeManager`, the built-in DI/IA/ Robotics model provider, and the ordered Robotics configuration pipeline. It owns the DI namespace, so it cannot be combined with `AddOpcUaDi()`. ```csharp services .AddOpcUa() .AddServer(options => { /* endpoint and application options */ }) .AddRobotics(options => options.InstanceNamespaceUri = "urn:example:robot-cell") .AddRoboticsModel() .ConfigureRobotics(async context => { await context.AddMotionDeviceSystemAsync("RobotCell", system => { // Add controllers, motion devices, axes, power trains, and // safety states through the fluent Robotics builders. }, context.CancellationToken); }); ``` Use `ConfigureRobotics()` for a dependency-injected class-based code-behind implementing `IRoboticsConfigurator`, and `ConfigureRoboticsFor(…)` when the application already owns a compatible `DiNodeManager`. Configurators run in registration order and share one `IRoboticsBuildContext` per manager startup. See the [Robotics developer guide](Robotics.md). Client factories compose the Robotics client over the managed session. `AddRoboticsClient()` also registers the DI client services because the Robotics client extends `DiTopologyClient`: ```csharp services.AddOpcUa() .AddClient(options => { /* endpoint and application options */ }) .AddRoboticsClient(); ``` ## Client feature `builder.AddClient(opt => …)` registers a lazy `ManagedSession` factory delegate. The factory caches the connected session — subsequent awaits return the same instance. ```csharp using Microsoft.Extensions.DependencyInjection; using Opc.Ua.Client; services .AddOpcUa() .AddClient(opt => { opt.ApplicationName = "MyClient"; opt.ApplicationUri = "urn:localhost:MyClient"; opt.ProductUri = "uri:example.com:MyClient"; opt.Session = new ManagedSessionOptions { Endpoint = endpoint, ReconnectPolicy = new ReconnectPolicyOptions { Strategy = BackoffStrategy.Exponential } }; }); // Resolve and connect on first use: var sessionFactory = sp.GetRequiredService>>(); ManagedSession session = await sessionFactory(ct); ``` `AddClient` also registers `ITelemetryContext`, `ISessionFactory` (a `DefaultSessionFactory` configured with the V2 subscription engine), `ManagedSessionFactory`, `IManagedSessionFactory`, and the top-level `OpcUaClientOptions`. The cached delegate is convenient for a single fixed endpoint; resolve `IManagedSessionFactory` when endpoints are selected at runtime: ```csharp var managedSessions = sp.GetRequiredService(); ManagedSession dynamicSession = await managedSessions.ConnectAsync(endpoint, ct); ``` Misconfiguration is validated through `IValidateOptions` when the host starts and again before a DI-created session connects. Supply either an explicit `Configuration`, the application identity fields directly on `OpcUaClientOptions` (as shown above), an existing configuration XML document (`ConfigurationFile` / `ConfigurationStream`, see below), or the shared `ConfigureApplication(...)` options for a combined client/server host (see [Shared application configuration](#shared-application-configuration)). `Session.Endpoint` is required by the cached fixed-endpoint delegate; `IManagedSessionFactory.ConnectAsync(endpoint, ...)` supplies it at runtime. ### Client: using an existing configuration XML file The client offers the same migration path as [the server](#migrating-with-an-existing-configuration-xml-file): applications that already own a classic client configuration file (`*.Config.xml`) can pass it to `AddClient` and keep every setting in it — security configuration, certificate stores, transport quotas, client configuration: ```csharp services .AddOpcUa() .AddClient("MyClient.Config.xml", opt => { opt.Session = new ManagedSessionOptions { Endpoint = endpoint }; }); ``` The same file can be referenced from configuration (`OpcUa:Client:ConfigurationFile`), and a stream works for documents that are not files on disk, e.g. embedded resources (`OpcUaClientOptions.ConfigurationStream`; read once and disposed after loading): ```csharp Stream stream = typeof(Program).Assembly .GetManifestResourceStream("MyApp.MyClient.Config.xml")!; services.AddOpcUa().AddClient(stream); ``` The document loads lazily through `ApplicationInstance.LoadApplicationConfigurationAsync` on first use — the first session connect, reverse-connect startup, or an explicit `GetAsync` on `OpcUaClientOptions.ConfigurationProvider` — which also runs the optional `ConfigureLoadedConfiguration` override callback and ensures the application-instance certificate, exactly like the shared `ConfigureApplication(...)` path. The supplied document is authoritative: it takes precedence over a shared application registered with `ConfigureApplication(...)`, and combining it with an explicit `Configuration`, with the application identity properties, or setting both file and stream is rejected with a clear `InvalidOperationException` at registration. DI-registered `ICertificateManager` / `ICertificatePasswordProvider` are honored. ### Client: loading the configuration eagerly Hosted applications whose user interface needs the `ApplicationConfiguration` before any session exists — a WinForms or WPF client whose main form takes the configuration in its constructor, for instance — can opt into an eager load with `OpcUaClientOptions.LoadConfigurationOnStart` (bindable from `OpcUa:Client:LoadConfigurationOnStart`). It registers an `IHostedService` that awaits the load while the host starts, the client-side twin of the reverse-connect hosted service: ```csharp services .AddOpcUa() .AddClient("MyClient.Config.xml", opt => opt.LoadConfigurationOnStart = true); using IHost host = builder.Build(); await host.StartAsync(); // The document is loaded and validated and the application instance // certificate is ensured; a failure has already failed host.StartAsync(). ApplicationConfiguration configuration = host.Services .GetRequiredService() .ConfigurationProvider! .Configuration; ``` `OpcUaClientOptions.ConfigurationProvider` is the public `IOpcUaApplicationConfigurationProvider` resolved for the client — the supplied-document provider when `ConfigurationFile` / `ConfigurationStream` was set, and otherwise the shared `ConfigureApplication(...)` provider. It is available on the options instance resolved from the service provider, not on the instance passed to the `AddClient(...)` callback, and it exposes everything a hosted client needs: `GetAsync(ct)` as an explicit load trigger without connecting a session, `Configuration` to read the loaded document back, and `Application` (`IApplicationInstance`) for applications that manage their own instance certificate. Applications that do not use the Generic Host can call `GetAsync` directly instead of setting `LoadConfigurationOnStart`; both routes share the provider's single-flight load, so the configuration is never loaded twice. `LoadConfigurationOnStart` applies to every provider source and is a no-op when an explicit `Configuration` was supplied, which is already complete. Because it decides a service registration, set it within the `AddClient(...)` callback or bind it from the configuration section; setting it on the resolved options afterwards has no effect. ### Fluent shortcuts The returned `IOpcUaClientBuilder` can compose client features without returning to the root builder: ```csharp services.AddOpcUa() .AddClient(options => { options.Configuration = configuration; options.Session = options.Session with { Endpoint = endpoint }; }) .AddSubscriptions() .AddManagedClientPool() .AddWotConClient() .AddGdsClient() .AddCertificateManagement(); ``` Reverse-connect one-shot sessions can be built through DI: ```csharp services.AddOpcUa() .AddReverseConnectClient(options => { options.Configuration = configuration; options.Session = options.Session with { Endpoint = endpoint }; }, new Uri("urn:server")); ``` Discovery-and-connect resolves `IOpcUaDiscoveryService`, selects an endpoint by security policy and mode, then connects through `IManagedSessionFactory`: ```csharp services.AddOpcUa() .AddClient(options => options.Configuration = configuration) .AddDiscoveryAndConnect(options => { options.DiscoveryUrl = "opc.tcp://localhost:4840"; options.SecurityMode = MessageSecurityMode.SignAndEncrypt; options.SecurityPolicyUri = SecurityPolicies.Basic256Sha256; }); ``` Client security checklist: - Select `MessageSecurityMode.SignAndEncrypt` and an approved policy such as `Basic256Sha256` (or a supported stronger policy) during discovery. Do not fall back to `SecurityPolicy#None` outside isolated lab environments. - Keep `AutoAcceptUntrustedCertificates = false`; provision the server or issuer certificate in the client trust store and retain URI/hostname, chain, revocation, key-size, and nonce validation. See [Certificates](Certificates.md). - Register an `IClientIdentityProvider` appropriate for the selected endpoint's user-token policy. Keep passwords and tokens in the secret/provider infrastructure rather than embedding them in configuration. See [Identity Providers](IdentityProviders.md). ### Identity (client) > Full identity surface is documented in > [Identity Providers](IdentityProviders.md). This section covers only > the dependency injection bindings. `builder.AddClient(...)` exposes: | Extension | Purpose | |---|---| | `AddIdentityProvider()` | Registers a custom `IClientIdentityProvider` type. All registered providers are composed into a single resolver by the session builder. | | `AddIdentityProvider(Action)` | Fluent composite-builder with shortcuts: `.AddAnonymous()`, `.AddUserName(...)`, `.AddX509(...)`, `.AddIssuedToken(...)`. | | `AddIdentityProvider(IConfiguration section)` | Binds `OpcUaClientIdentityOptions` and rolls a `CompositeClientIdentityProvider` from the bound entries. | | `AddAccessTokenProvider()` / `(instance)` / `(factory)` | Registers an `IAccessTokenProvider` keyed by `AuthorityUri`. Issued-token identity providers select the matching provider at runtime via the URI. | Configuration binding under `OpcUa:Client:Identity`: ```json { "OpcUa": { "Client": { "Identity": { "EnableAnonymous": true, "UserName": { "UserName": "alice", "SecretName": "alice-password", "SecretStoreType": "InMemory" }, "X509": { "StoreType": "X509Store", "StorePath": "CurrentUser/My", "SubjectName": "CN=Alice" }, "IssuedToken": { "ProfileUri": "http://opcfoundation.org/UA/UserToken#JWT", "AuthorityUri": "https://issuer.example" }, "Order": [ "IssuedToken", "UserName", "X509", "Anonymous" ] } } } } ``` When `Order` is non-empty the composite presents providers in that preference; otherwise registration order is preserved. The `IssuedToken` entry resolves the registered `IAccessTokenProvider` whose `AuthorityUri` matches. > **How the section is consumed.** `AddClient(IConfiguration)` binds > `OpcUa:Client:Identity` into `OpcUaClientOptions.Identity`. At > session-factory resolution time, if no `IClientIdentityProvider` > service is explicitly registered, the factory builds a composite > from the bound options provided at least one non-default field is > set (any of `UserName`, `X509`, `IssuedToken`, `Order`, or > `EnableAnonymous = false`). Setting only `EnableAnonymous = true` > (the default) does NOT register any provider — call > `.AddIdentityProvider(configuration.GetSection("OpcUa:Client:Identity"))` > if you want an explicit eager registration regardless of the option > values, or use the > `AddIdentityProvider(Action)` > overload for code-only configuration. The composite-builder > shortcuts (`.AddUserName(configure, registry)`, > `.AddX509(configure, provider, passwords)`, and > `.AddIssuedToken(configure, provider)`) each take the supporting > service (an `ISecretRegistry`, `ICertificateProvider` + > `ICertificatePasswordProvider`, or `IAccessTokenProvider`) as a > second positional argument. `ManagedSessionOptions.Identity` (eager) is `[Obsolete]` — set `ManagedSessionOptions.IdentityProvider` instead for lazy / refreshable identities. The `ManagedSession` proactive-refresh scheduler keys off `IClientIdentityProvider.ExpiresAt`. ## Complex types ```csharp services.AddOpcUa().AddClient(/* … */).AddComplexTypes(); ``` `AddComplexTypes()` can be chained directly after `AddClient(...)`. It registers a `ComplexTypeSystemFactory` (singleton) that can be resolved and used to build a `ComplexTypeSystem` for a connected session: ```csharp var factory = sp.GetRequiredService(); ComplexTypeSystem cts = factory.Create(session); await cts.LoadAsync(...); ``` For a managed client that should load complex types automatically after connect, use either `ManagedSessionBuilder.WithLoadComplexTypes()` or set `ManagedSessionOptions.LoadComplexTypes = true`. The one-shot `AddManagedClient(...)` helper registers client services, reconnect defaults, and complex-type loading together: ```csharp services.AddOpcUa().AddManagedClient(opt => { opt.Configuration = applicationConfiguration; opt.Session = new ManagedSessionOptions { Endpoint = endpoint }; }); ``` The direct constructor fallback remains available: ```csharp var cts = new ComplexTypeSystem(session, telemetry); await cts.LoadAsync(ct: ct); ``` ## Alarms and conditions ```csharp services.AddOpcUa().AddClient(/* … */).AddAlarms(); ``` Registers a singleton `AlarmClientFactory` so dependency-injection-hosted client applications can obtain a Part 9 `AlarmClient` per connected session without `new`-ing one manually. Source-generated event records (`ConditionTypeRecord`, `AlarmConditionTypeRecord`, `DialogConditionTypeRecord`, all subtypes including vendor extensions) and the streaming extensions (`SubscribeAlarmsAsync`, `SubscribeConditionsAsync`, `SubscribeDialogsAsync`) require no registration — they compose naturally with the `ManagedSession.DefaultStreaming` surface that `AddClient(...)` already wires up. ```csharp ManagedSession session = await sessionFactory(ct); var factory = sp.GetRequiredService(); AlarmClient alarms = factory.Create(session); // Acknowledge an alarm: await alarms.AcknowledgeAsync(conditionId, eventId, new LocalizedText("en", "Acknowledged"), ct); // Stream typed records via the session's default streaming subscription: await foreach (ConditionTypeRecord record in session.DefaultStreaming .SubscribeAlarmsAsync(ObjectIds.Server, ct: ct)) { /* … */ } ``` The non-dependency-injection path (`session.GetAlarmClient()` extension and the public `AlarmClient` constructor) remains available for callers that do not use the dependency injection infrastructure. See [Alarms and Conditions](AlarmsAndConditions.md) for the full developer guide. ### Client-side reverse connect When `OpcUaClientOptions.ReverseConnect` is set, the dependency injection container registers a singleton `ReverseConnectManager` that opens the configured listener endpoints on first resolution. Inbound reverse-hello messages are surfaced via `ReverseConnectManager.WaitForConnectionAsync(endpointUrl, serverUri, ct)`, and the values are also mirrored into `ApplicationConfiguration.ClientConfiguration.ReverseConnect`. ```jsonc { "OpcUa": { "Client": { "ReverseConnect": { "HoldTimeMs": 15000, "WaitTimeoutMs": 20000, "ClientEndpointUrls": [ "opc.tcp://0.0.0.0:4841" ] } } } } ``` Equivalent code-only: ```csharp services.AddOpcUa().AddClient(opt => { opt.Configuration = applicationConfiguration; opt.ReverseConnect = new ClientReverseConnectOptions(); opt.ReverseConnect.ClientEndpointUrls.Add("opc.tcp://0.0.0.0:4841"); }); // Resolve the manager and await an inbound reverse-hello connection: var reverseConnect = sp.GetRequiredService(); ITransportWaitingConnection connection = await reverseConnect.WaitForConnectionAsync(endpointUrl, serverUri: null, ct); // pass `connection` to Session.Create / DefaultSessionFactory.RecreateAsync ``` Note: the `Func>` delegate registered by `AddClient` does **not** automatically consume the reverse-connect manager for initial connection — it still dials the configured endpoint outbound. Use the `ReverseConnectManager` directly when the server initiates the session. The `ReverseConnect` configuration is also consumed by `Session` / `SessionReconnectHandler` for reconnect-via-reverse-hello scenarios. ## Channel manager `AddClient(...)` automatically registers an `IClientChannelManager` singleton (`ClientChannelManager` implementation) built from `OpcUaClientOptions.Configuration`. The `Func>` delegate wires this manager into every new `ManagedSession` it creates, so multiple `ManagedSession` instances resolved from the same DI container will share underlying transport channels per `ConfiguredEndpoint`. Reconnect is coalesced and notified to attached sessions transparently. On .NET 8 and later, HTTPS transport channels also get the named `IHttpClientFactory` client (`Opc.Ua.Client`) and its standard resilience handler through `AddClient(...)`. See [Sessions and reconnect § 4](Sessions.md#4-iclientchannelmanager--centralised-channel-sharing-and-reconnect) for details on identity, retry policy, HTTPS request resilience, and the shared retry budget between channel-manager reconnect and `ManagedSession`'s outer `IReconnectPolicy`. > **Security trade-off — OPC UA TLS validation vs the DI HttpClient pipeline.** > When an OPC UA `CertificateValidator` is configured for a channel > (the normal case for any non-`SecurityPolicies.None` HTTPS profile), > `HttpsTransportChannel` always takes the direct construction path so > the OPC UA trust list, OPC UA mutual TLS, the redirect lock, and the > message-size quotas are guaranteed. The named `Opc.Ua.Client` > HttpClient pipeline (Polly resilience handler, etc.) is **not** > applied to those channels and the channel emits a one-time > `LogWarning` to make the bypass visible. To use both the Polly > resilience handler AND OPC UA cert validation on the same channel, > register the named client with a `ConfigurePrimaryHttpMessageHandler` > that wires the OPC UA `ServerCertificateCustomValidationCallback` and > the OPC UA application instance certificate yourself. See > [Sessions and reconnect § HTTPS factory + OPC UA cert validation: secure-by-default fallback](Sessions.md#https-factory--opc-ua-cert-validation-secure-by-default-fallback). To override the default channel manager (e.g. to inject a custom `IChannelReconnectPolicy`): ```csharp services.AddOpcUa().AddClient(opt => { ... }); services.Replace(ServiceDescriptor.Singleton(sp => new ClientChannelManager( sp.GetRequiredService().Configuration!, sp.GetRequiredService(), reconnectPolicy: new ExponentialBackoffChannelReconnectPolicy { MaxAttempts = 5 }))); ``` ## Application instance (advanced) ```csharp services.AddOpcUa().AddApplicationInstance(); ``` Registers an `IApplicationInstanceFactory` singleton. Hosted server services (regular, GDS, LDS, WotCon) call the factory to create their own per-host `IApplicationInstance` instead of sharing a process-wide singleton — this avoids configuration overwrite and certificate-lifecycle conflicts when multiple servers coexist in one process. `AddServer` / `AddGdsServer` / `AddLdsServer` register the factory automatically, so calling `AddApplicationInstance()` explicitly is only needed when you want the factory available outside the hosted-server context. ## GDS Client ```csharp services.AddOpcUa().AddGdsClient(opt => { opt.SessionTimeout = TimeSpan.FromMinutes(2); opt.MaxConnectAttempts = 10; }); ``` Requires an `ApplicationConfiguration` registered in the container. Resolves `GlobalDiscoveryServerClient` and `ServerPushConfigurationClient` singletons. ## GDS Server ```csharp services .AddOpcUa() .AddGdsServer(o => { o.ApplicationName = "MyGds"; o.ApplicationUri = "urn:localhost:MyOrg:MyGds"; o.ProductUri = "uri:myorg:mygds"; o.AutoAcceptUntrustedCertificates = false; o.EndpointUrls.Add("opc.tcp://localhost:58810/GlobalDiscoveryServer"); o.AuthoritiesStorePath = "%LocalApplicationData%/OPC Foundation/pki/CA"; }) .AddApplicationsDatabase() .AddCertificateGroup() .AddCertificateRequest() .AddUserDatabase(); .AddInMemoryStores() // Optionally: // .AddAuthorizationService(o => o.IssuerUri = "urn:my-gds") // .AddAccessTokenProvider() // .AddKeyCredentialRequestStore() // .AddConfigurationDataStore(); ``` Throws on a second `.AddGdsServer(...)`. Auto-registers an internal `GdsHostedServer : GlobalDiscoverySampleServer` and its `ApplicationsNodeManager`. The pluggable services (`IApplicationsDatabase`, `ICertificateGroup`, `ICertificateRequest`, etc.) are resolved from the container at startup. ### Identity (GDS server) `IGdsServerBuilder` forwards every identity-related extension to the underlying server builder so a GDS host configures identity the same way as a regular server: ```csharp services .AddOpcUa() .AddGdsServer(opt => opt.ApplicationName = "MyGds") .AddDefaultIdentityAuthenticators(opt => { opt.EnableAnonymous = false; opt.EnableUserNamePassword = true; }) .AddJwtIssuer(opt => { opt.IssuerUri = "https://login.microsoftonline.com/{tenant}/v2.0"; opt.JwksUri = "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys"; }); ``` `Action<>` and `IConfiguration` overloads are available for `ConfigureRoles`, `AddDefaultIdentityAuthenticators`, and `AddJwtIssuer`. `AddIdentityAugmenter()` registers post-authentication identity augmenters; `AddGdsApplicationSelfAdminProvider()` registers the built-in OPC 10000-12 §7.2 SelfAdmin provider and is also wired by the GDS `AddDefaultIdentityAuthenticators(...)` helper. `AddAuthorizationService(...)` enables the default `CertificateJwtIssuer`; `AddAuthorizationService(...)` registers a custom `ITokenIssuer` for cloud KMS, HSM, or external token-service signing. `GdsServerHostedService` consumes these forwarded registrations during startup and adds them to the same identity registry used by regular OPC UA hosted servers. See [Identity Providers](IdentityProviders.md) for the full reference. ## LDS Server ```csharp services .AddOpcUa() .AddLdsServer(o => { o.ApplicationName = "MyLds"; o.ApplicationUri = "urn:localhost:MyOrg:MyLds"; o.ProductUri = "uri:myorg:mylds"; o.EndpointUrls.Add("opc.tcp://localhost:4840/UADiscovery"); o.EnableMulticast = true; // LDS-ME multicast advertisement o.MulticastLoopbackOnly = false; // set true for in-process tests o.ServerCapabilities.Add("DA"); // extra capabilities (LDS / LDS-ME are always implicit) }); ``` Throws on a second `.AddLdsServer(...)`. The server is advertised as `ApplicationType.DiscoveryServer` (the LDS class promotes the type after `ApplicationConfiguration.CreateAsync`). `MulticastLoopbackOnly` restricts the mDNS announcer to the loopback NIC — intended for in-process tests where LDS-ME traffic must stay local. `ServerCapabilities` is additive — `LDS` is always included, plus `LDS-ME` when `EnableMulticast` is `true`. ## WoT Connectivity Server The WoT Connectivity server lives inside a regular `StandardServer`, so `AddWotConServer` must be combined with `AddServer`: ```csharp services .AddOpcUa() .AddServer(o => /* regular server options */) .AddNodeManager() // your domain node managers .Services .AddOpcUa() // returns the same builder .AddWotConServer(o => { o.AssetNamespaceUri = "http://myorg/UA/WoT-Con/Assets/"; o.ThingDescriptionStorageFolder = "tds"; }) .AddAssetProvider() .AddDiscoveryProvider(); ``` `AddWotConServer` registers the WoT `WotConnectivityNodeManagerFactory` as an `OpcUaServerNodeManagerRegistration` so it attaches to the regular server feature. `IWotAssetProviderFactory` and `IWotAssetDiscoveryProvider` services registered in dependency injection are picked up automatically. If `AddWotConServer` is registered without a preceding `AddServer`, startup fails with a clear configuration error instead of silently dropping the node manager registration. ## WoT Connectivity Client ```csharp services.AddOpcUa().AddClient(/* … */).AddWotConClient(); // Resolve and connect on first use: var wotClient = sp.GetRequiredService>>(); WotConnectivityClient client = await wotClient(ct); ``` The WoT client reuses the connected `ManagedSession` registered by `AddClient(...)`. ## Asset Administration Shell V3 The AAS metamodel server is hosted inside the regular OPC UA server. Register custom providers first; `AddAasV3Server` uses `TryAddSingleton` defaults for the document-backed value provider and operation handler. ```csharp services.AddSingleton(); services .AddOpcUa() .AddServer(options => { /* endpoint and application options */ }) .Services .AddOpcUa() .AddAasV3Server(options => { options.EnvironmentFolder = "aas-environments"; options.RetirementPolicy = AasProjectionRetirementPolicy.Graceful; }) .AddOperationHandler(); ``` `AddAasV3Client` registers a metamodel client factory over the connected `ManagedSession`; `AddAasV3RegistryClient` registers the registry client factory that resolves `Server/AASRegistry` on first use: ```csharp services .AddOpcUa() .AddClient(options => { /* endpoint and application options */ }) .AddAasV3Client(options => { options.InstanceNamespaceUri = "urn:example:aas:instances"; }) .AddAasV3RegistryClient(); ``` See [OPC UA for Asset Administration Shell V3](Aas.md). ## Combined hosts You can run a regular server, a GDS server, and an LDS server inside a single Generic Host. Each owns its own: - `OpcUaServerOptions` / `GdsServerOptions` / `LdsServerOptions` - PKI root and certificate - Endpoint URLs and ports - `IApplicationInstance` (created from the shared `IApplicationInstanceFactory`) - `BackgroundService` (the registrations don't collide) ```csharp builder.Services .AddOpcUa() .AddServer(o => /* regular server on 51210 */) .AddNodeManager() .Services .AddOpcUa() .AddLdsServer(o => /* LDS on 4840 */) .Services .AddOpcUa() .AddGdsServer(o => /* GDS on 58810 */) .AddApplicationsDatabase() .AddCertificateGroup() .AddCertificateRequest() .AddUserDatabase(); ``` Node managers registered under a feature wrapper (e.g. `OpcUaServerNodeManagerRegistration` for the regular server) are isolated: the GDS / LDS hosted services do not see them. ## Native AOT Every `.AddXxx(...)` overload — both the `Action` shape and the `IConfiguration` / `IConfigurationSection` shapes — is AOT-safe. The .NET 8+ Configuration Binding Source Generator is enabled on every library that performs configuration binding (`true`), so the reflection-based binder is replaced by statically-generated [C# 12 interceptors](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-12#interceptors) at compile time. ```csharp services.AddOpcUa() .AddServer(builder.Configuration.GetSection("OpcUa:Server")) // bind from appsettings.json — AOT-safe .AddNodeManager(); ``` `Action` (code-only) and `IConfiguration` overloads can be mixed freely; consumers no longer need to choose between them for AOT compatibility. Notes: - The source generator targets net8.0+. On older TFMs (net48 / netstandard2.0 / netstandard2.1) the generator is a no-op and the reflection-based binder is used — those TFMs don't support PublishAot anyway. - Options properties whose type is an interface or a non-default- constructible class (e.g. `ApplicationConfiguration`, `IUserIdentity`, `ISubscriptionEngineFactory`) are silently skipped by the generator with an informational `SYSLIB1100` / `SYSLIB1101` diagnostic. Those properties are runtime-only — set them in code, not in `appsettings.json`. The affected libraries suppress those diagnostics in their csproj. - The `tests/Opc.Ua.Aot.Tests` project verifies the end-to-end AOT path: build + AOT publish produce **zero** `IL2026` / `IL3050` warnings from any dependency injection extension. For AOT consumers, configure options through code or configuration — both are supported: ```csharp services.AddOpcUa() .AddServer(o => { o.ApplicationName = "AotServer"; o.ApplicationUri = "urn:host:AotServer"; o.EndpointUrls.Add("opc.tcp://localhost:51210/AotServer"); o.AutoAcceptUntrustedCertificates = false; }) .AddNodeManager(); ``` ## Telemetry `AddOpcUa()` registers an `ITelemetryContext` that resolves the host's `ILoggerFactory` on first use. To override: ```csharp services.AddSingleton(myCustomTelemetry); services.AddOpcUa(); // TryAddSingleton — custom one wins ``` To configure logging fluently: ```csharp services.AddOpcUa() .AddLogging(b => b.AddConsole().SetMinimumLevel(LogLevel.Information)) .AddServer(/* … */); ``` ## See also - [Sessions](Sessions.md) — `ManagedSession`, reconnect, subscription engines. - [Source Generated NodeManagers](NodeManagers.md#source-generated-node-managers) — `IAsyncNodeManagerFactory` from a model design XML. - [Native AOT](NativeAoT.md) — AOT testing setup. - [GDS Developer Guide](GDS.md) — GDS service interfaces and provider patterns. - [Robotics](Robotics.md) — OPC 40010 hosting, model providers, and topology builders. - [WoT Connectivity](WoTConnectivity.md) — OPC 10100-1 information model. - [Diagnostics](Diagnostics.md) — `ITelemetryContext` end-to-end. - [CryptoProvider](CryptoProvider.md) — `AddCryptoProvider`, hardware-held private keys, FIPS posture and audit.