# Description Adds **triggered-item (SetTriggering) support to the V2 (modern) subscription engine** with a hybrid API, batched apply pipeline, and automatic replay on recreate/reconnect — closing the gap from #3834 and adding store/load round-trip for triggering relationships. ## Public API | Concern | Surface | |---|---| | Declarative | `MonitoredItemOptions.TriggeredByNames` (`IReadOnlyList<string>`) — set at item-add time | | Imperative | `ISubscription.SetTriggeringAsync(IMonitoredItem triggeringItem, IReadOnlyCollection<IMonitoredItem>? linksToAdd, IReadOnlyCollection<IMonitoredItem>? linksToRemove, CancellationToken ct)` returning `SetTriggeringResult` with per-link statuses | | Fluent helpers | name-based overloads `SetTriggeringAsync(this ISubscription, string triggeringItemName, params string[] triggeredItemNames)` (and `add`/`remove`/`ct` variant) | | N:M navigation | `IMonitoredItem.TriggeringItems` (items that trigger this) and `IMonitoredItem.TriggeredItems` (items I trigger), both on-demand projections | | Result type | `public sealed record SetTriggeringResult(IMonitoredItem TriggeringItem, IReadOnlyList<(IMonitoredItem Item, StatusCode Status)> AddResults, IReadOnlyList<(IMonitoredItem Item, StatusCode Status)> RemoveResults, StatusCode ServiceResult)` | No client handles surface on the public API. Validation in the imperative path is by **reference identity** under the manager lock — items from a different subscription with a matching name cannot sneak through, and a removed-then-re-added item with the same name does not silently re-target. ## Engine — batched apply A new manager-level operations queue + a batched apply step in `MonitoredItemManager.ApplyMonitoredItemChangesAsync`: 1. After Create / Modify / SetMonitoringMode, drain the operations queue. 2. Group by triggering item (reference identity). 3. Re-queue ops whose triggering item is not yet `Created` (bounded by `MaxTriggeringRetryCount`; permanently failed items propagate the create-failure status to the operation TCS). 4. Fold per-edge across operations targeting the same triggering item — last-intent wins; same-edge add+remove resolves to one net operation. 5. Pre-resolve: triggered items with `Created=false` resolve to `Bad_MonitoredItemIdInvalid` (adds) or `Good` no-op (removes, per Part 4 §5.13.1.6 server auto-cleanup). 6. Issue **one `SetTriggering` RPC per triggering item** carrying both `linksToAdd` and `linksToRemove` (the server processes remove before add per §5.13.5.2 — atomic swap semantics). 7. Map per-link results back to contributing operations; complete each TCS with its own `SetTriggeringResult` subset. Imperative writes mutate the per-item runtime `DesiredTriggeredByNames` synchronously so navigation (`TriggeringItems`/`TriggeredItems`) and `Snapshot()` reflect intent before the RPC fires. Per-link failures roll back the optimistic mutation so the client view matches server reality. ## State ownership `MonitoredItemOptions.TriggeredByNames` is the **initial declarative input**. The canonical runtime source of truth is a per-item `DesiredTriggeredByNames` field, initialized from options and mutated by both the imperative API and subsequent options changes. The field is preserved across `Reset` (recreate path), which is what enables automatic SetTriggering replay after recreate. ## Save / load (closes #3834) - `MonitoredItemStateSnapshot.TriggeredByNames` (`ArrayOf<string>`) replaces the old `TriggeringItemClientHandle` field (V2-only — not yet shipped, so a clean break; classic snapshot format unchanged). - `Snapshot()` reads from runtime `DesiredTriggeredByNames`; `ApplyLoadState` writes loaded names into both options and runtime state without leaking through the OnChange tracker. - **TransferSubscriptions** success path: only the local view is restored — trusting the spec (`§5.13.5`) that servers preserve triggering links across subscription transfer. No SetTriggering RPC is issued. - **Recreate-fallback** (transfer rejected), **in-session `RecreateAsync`**, and **Reconnect** paths: `Reset` preserves `DesiredTriggeredByNames` and enqueues an add-delta per preserved triggering name; the batched apply pass replays SetTriggering after items finish (re)creating. ## Error handling Per OPC UA Part 4 §5.13.5.3/§5.13.5.4 (Tables 73/74): | Condition | Engine response | |---|---| | Per-link `Bad_MonitoredItemIdInvalid` | Surfaced in `SetTriggeringResult.AddResults`/`RemoveResults`; optimistic `DesiredTriggeredByNames` mutation rolled back | | Service-level `Bad_SubscriptionIdInvalid` | Operations re-queued, not terminal — replays after the subscription state machine recreates | | Service-level `Bad_TooManyOperations` | Surfaced as `ServiceResultException` to all contributing TCSs (no auto-chunking in this version) | | Triggering item never reaches `Created` | After `MaxTriggeringRetryCount` re-queues, op completes with the triggering item's propagated error (no infinite loop) | | Communication errors | Routed through the existing retry policy with bounded retry count | ## Spec consultation Cross-checked against **OPC UA Part 4 v1.05.07 §5.13.5** (verified Table 73 / Table 74 directly via the OPC Foundation spec site) and §5.13.1.6 (deletion side-effects, monitoring-mode interactions). The only per-link spec-specific status code is `Bad_MonitoredItemIdInvalid`; `linksToRemove` is processed server-side before `linksToAdd` (driving the single-RPC-per-trigger batch shape). ## Tests - **9 new unit tests** in `MonitoredItemManagerTriggeringTests.cs` (single op, multi-op merge into one RPC, per-trig grouping, requeue-when-not-Created, per-link rollback, `Bad_SubscriptionIdInvalid` recovery, deletion races, last-intent same-edge conflict resolution). - **Updated** integration tests `SetTriggeringTrackingV2Async` and `SnapshotCapturesTriggeringForReplayAsync` to use the new API. - **2 new integration tests** in `SubscriptionTests.cs`: `DeclarativeTriggeredByNamesV2Async` (options-driven path end-to-end) and `NToMTriggeringV2Async` (one triggered item linked to two trigger items, per-link removal). - **AOT smoke test** in `SubscriptionAotTests.cs` for the `MonitoredItemStateSnapshot.TriggeredByNames` binary encoder/decoder round-trip. - All **344 V2 subscription/monitored-item unit tests** and **71 V2 subscription integration tests** pass locally (net10). ## Documentation - New: `Docs/Subscriptions.md` — *Subscriptions and Monitored Items Service Set*. Merges the previous `StreamingSubscription.md` and `SubscriptionTriggering.md` into a single guide; leads with a Classic-vs-V2 engine overview (recommending V2 for new code) and links to `Sessions.md` §4 for the full engine comparison. Covers the V2 callback API, declarative + imperative SetTriggering with N:M, save/load/restore behavior, error handling, and the `IStreamingSubscription` async-enumerable surface. - Updated: `Docs/README.md`, `Docs/AlarmsAndConditions.md`, `Docs/StateMachines.md`, `Docs/Profiles.md` — all point to the new merged doc. - Updated: `plans/26-v2-subscription-parity.md` — rows 49 (`SetTriggeringAsync`) and 111 (`TriggeringItems` plural / `TriggeredItems`). ## Related Issues - Fixes #3834 — `TransferSubscription does not automatically restore/retain links between trigger and monitored items`. The fix is two-pronged: (a) transfer preserves links server-side per spec and the client snapshot captures `TriggeredByNames` so the local view restores automatically; (b) when transfer fails and the engine falls back to recreate (or when `RecreateAsync` is invoked explicitly), `Reset` enqueues per-triggering-name add-deltas from the preserved runtime state and the batched apply pass replays `SetTriggering` after re-creation. ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received.
OPC UA .NET Stack
Overview
This OPC UA reference implementation targets .NET Framework, .NET, and .NET Standard 2.1.
.NET allows to develop apps that run on all common platforms available today, including Linux, iOS, Android (via Xamarin) and Windows without requiring platform-specific modifications.
One of the reference implementations inside this project has been certified for compliance through an OPC Foundation Certification Test Lab to prove its high quality. Fixes and enhancements since the certification process have been tested and verified for compliance using the latest Compliance Test Tool (CTT).
For a comprehensive list of supported OPC UA Profiles and Facets, see the dedicated documentation.
More samples based on the official Nuget packages can be found in the OPC UA .NET Samples repository. For development there is also a preview Nuget feed available. For local testing a Docker container of the Reference Server is available for preview and release builds.
Features included
Core and Libraries
- Fully ported Core OPC UA Stack and Libraries (Client, Server, Configuration, Complex Types & GDS assemblies).
- Reference sample Server and Client.
- X.509 Certificate support for client and server authentication.
- SHA-2 support (up to SHA512) including security profile Basic256Sha256, Aes128Sha256RsaOaep and Aes256Sha256RsaPss for configurations with high security needs.
- ECC Security policies ECC_nistP256, ECC_nistP384, ECC_brainpoolP256r1 and ECC_brainpoolP384r1.
- Anonymous, username and X.509 certificate user authentication.
- UA-TCP & HTTPS transports (client and server).
- Reverse Connect for the UA-TCP transport (client and server).
- Folder & OS-level (X509Store) Certificate Stores with Global Discovery Server and Server Push support.
- Sessions and (durable) Subscriptions.
- A PubSub library with samples.
New in 2.0
- Type safe built in types
- Source generator generated code behind during build
- See MigrationGuide for details.
- New AsyncCustomNodeManager (successor of CustomNodeManager2) with improved Locking Strategy, see Server Async (TAP) Support
- In our Load Test the Server shows at least 2.5x higher throughput under load with 750 subscriptions totaling 450k Monitored items and write times for 600 items below 5 seconds were before > 10 seconds were needed. Also for event at least 3x faster event reporting was observed.
- Unified Dependency Injection surface —
services.AddOpcUa()with fluentAddServer()/AddClient()/AddComplexTypes()/AddAlarms()/AddGdsServer()/AddLdsServer()/AddWotConServer()extensions forMicrosoft.Extensions.DependencyInjectionand the .NET Generic Host (AOT compatible via theAction<TOptions>overloads).
New in 1.05.378
- Support for AsyncNodeManagers in the Server Library, see Server Async (TAP) Support
- Reworked Observability via
ITelemetryContextin preparation for better DI support. See documentation for breaking changes. - Fully async APIs. Deprecated all sync and APM API.
New in 1.05.376
- Support for Durable Subscriptions
New in 1.05.375
- Support for ECC Certificates.
New in 1.05.374
- CRL Support for the X509Store on Windows
New in 1.05.373
- 1.05 Nodeset
- Support for WellKnownRoles & RoleBasedUserManagement.
Key Features and Updates in OPC UA 1.05
- Thread Safety and Locking: Improved thread safety and reduced locking in secure channel operations.
- Audit and Redaction: New interfaces for auditing and redacting sensitive information.
- Security Enhancements: Improved encryption and authentication mechanisms.
- CRL Support: Added Certificate Revocation List support for X509Store on Windows.
- Performance Improvements: Faster binary encoding and decoding, reducing memory usage and latency.
- Role-Based Management: Support for WellKnownRoles and RoleBasedUserManagement WellKnownRoles & RoleBasedUserManagement.
- Improved Logging: Enhanced logging with
ILoggerandEventSource. - ECC Profiles: Support for NIST & Brainpool Security Profiles.
- Durable Subscriptions: Support for Durable Subscriptions Durable Subscriptions.
New in 1.04.368
- Improved support for Logging with
ILoggerandEventSource. - Support for custom certificate stores with refactored
ICertificateStoreandCertificateStoreTypeinterface. - Client and Server support for TransferSubscriptions.
- How to use Container support with reference server.
Samples and Nuget packages
- OPC UA Console Reference Server for .NET. A Linux Container of the latest builds is available. See also Container support.
- The OPC UA Reference Server has been certified for compliance through an OPC Foundation Certification Test Lab. Fixes and enhancements since the certification process have been tested and verified for compliance using the Compliance Test Tool (CTT). All releases are verified for compliance with the latest official Compliance Test Tool by the maintainers.
- OPC UA Console Reference Publisher and Console Reference Subscriber for .NET and .NET Framework.
- An official OPC UA Nuget package of the core, client, server and configuration libraries is available for integration in .NET projects. Note: The package has been split into Core, Client and Server packages to reduce the dependencies in projects where only client or server is needed. The https binding is now a seperate optional package.
- A preview Nuget feed is available from Azure Devops.
Getting Started
All the tools you need to build .NET libraries and application come with the .NET SDK. You need the .net 10 SDK to build the project. To run applications you need one of the target .NET runtimes installed. See Get started with .NET for what you need.
How to build and run the reference samples in Visual Studio on Windows
- Open the
UA.slnxsolution file using Visual Studio 2026. Ensure you have all target frameworks enabled, including .NET 4.8.x, .NET 8.0, 9.0, and 10.0 (LTS) but Visual Studio will prompt you to install what is missing. - Choose a project in the Solution Explorer and set it with a right click as
Startup Project. - Hit
F5to build and execute the sample.
How to build and run the console samples on Windows, Linux and iOS
This section describes how to run the ConsoleReferenceServer sample application.
Please follow instructions in this article to setup the .NET 10 SDK which provides the dotnet command that allows you to build and run the samples on your platform. Once the dotnet command is available, navigate to the root folder in your local copy of the repository and execute dotnet restore 'UA.slnx'. This command calls into NuGet to restore the tree of dependencies.
Start the server
- Open a command prompt.
- Navigate to the folder Applications/ConsoleReferenceServer.
- To run the server sample type
dotnet run --project ConsoleReferenceServer.csproj -a.- The server is now running and waiting for connections.
Contributing
We strongly encourage community participation and contribution to this project. First, please fork the repository and commit your changes there. Once happy with your changes you can generate a 'pull request'.
You must agree to the contributor license agreement (CLA) before we can accept your changes. The CLA and "I AGREE" button is automatically displayed when you perform the pull request.
Remarks
Self signed certificates for the sample applications
All required application certificates for OPC UA are created at the first start of each application in a directory or OS-level certificate store and remain in use until deleted from the store. Please read Certificates for more information about certificates and stores
Local Discovery Server
By default all sample applications are configured to register with a Local Discovery Server (LDS). A reference implementation of a LDS for Windows can be downloaded here. To setup trust with the LDS the certificates need to be exchanged or registration will fail.