Summary
The fluent node manager can observe a monitored item only after the default sampled item has been created (INodeBuilder.OnMonitoredItemCreated). There is no fluent way to take part in the creation itself: to refuse a request (unsupported filter, index range, or encoding) with the proper status code, or to substitute a custom MonitoredItem implementation that pushes values directly instead of being sampled. Managers that need this keep overriding the CreateMonitoredItemAsync family on AsyncCustomNodeManager.
Where the limit is
src/Opc.Ua.Server/NodeManager/AsyncCustomNodeManager.cs: CreateMonitoredItemAsync (~6461) is the virtual that decides everything: it validates the filter, calls ReadInitialValue (~6537), creates the sampled item, and only then calls OnMonitoredItemCreated (~6550). By the time the fluent hook fires, a refusal or substitution is no longer possible.
src/Opc.Ua.Server/Fluent/FluentDelegates.cs: MonitoredItemCreatedHandler receives an ISampledDataChangeMonitoredItem, i.e. it is post-creation by type.
src/Opc.Ua.Server/Fluent/IFluentDispatcher.cs: no pre-creation route.
- The generated partial (
NodeManagerTemplates.cs) does not override CreateMonitoredItemAsync, so a user partial can, but that is the pre-fluent pattern and it cannot be expressed per node.
Who needs it (from the UA-.NETStandard-Samples audit)
| Sample |
What it does at creation time |
Opc.Ua.Sample/MemoryBuffer |
Refuses filters (BadFilterNotAllowed), index ranges (BadIndexRangeInvalid) and encodings (BadDataEncodingUnsupported) at creation; creates its own MemoryBufferMonitoredItem : MonitoredItem that carries the node handle and is fed by the buffer's scan loop; overrides modify, delete and set-monitoring-mode to keep that item in sync. The sample tests assert those three status codes at creation, so the default sampled path (which accepts filters) cannot be used |
Workshop/PerfTest |
Subscribes register slices in the batch OnCreateMonitoredItemsComplete and pushes DataValues straight into IDataChangeMonitoredItem2.QueueValue every 45 ms for half a register, deliberately bypassing sampling; that push is the point of the sample |
Workshop/Aggregation |
Overrides the public CreateMonitoredItemsAsync because the batch completion hook is synchronous and it needs to await the downstream subscription |
Proposal
A pre-creation hook on the builder that can refuse or substitute, addressable per node, per type definition, or for a family of virtual nodes:
builder.Node("Buffers/UInt32")
.OnCreateMonitoredItem((ISystemContext ctx, NodeHandle handle, MonitoredItemCreateRequest request) =>
{
if (request.RequestedParameters.Filter != null) return MonitoredItemDecision.Refuse(StatusCodes.BadFilterNotAllowed);
if (request.ItemToMonitor.ParsedIndexRange != NumericRange.Empty) return MonitoredItemDecision.Refuse(StatusCodes.BadIndexRangeInvalid);
return MonitoredItemDecision.Use(buffer.CreateDataChangeItem(handle, request)); // custom IMonitoredItem
// or MonitoredItemDecision.Default() to fall through to the sampled item
});
FluentNodeManagerBase (generated partial and FluentNodeManager) would override CreateMonitoredItemAsync and consult the dispatcher before the default path runs.
- A substituted item must still flow through the existing modify / delete / set-monitoring-mode plumbing, so this pairs with the lifecycle hooks proposed separately (
OnMonitoredItemModified / Deleted / OnMonitoringModeChanged).
- An async batch
OnCreateMonitoredItemsCompleteAsync (the existing OnCreateMonitoredItemsComplete is sync) would cover the Aggregation and PerfTest batch cases without a public-method override.
- For push-style sources the handler needs a way to hand values to the item later; exposing the created
IDataChangeMonitoredItem2 (or an IValueUpdater-like handle bound to the item rather than to the node) from the decision would let PerfTest keep its direct-queue push under the fluent surface.
Related: #4397 (on-demand nodes), #4398 (monitored-item lifecycle hooks).
Summary
The fluent node manager can observe a monitored item only after the default sampled item has been created (
INodeBuilder.OnMonitoredItemCreated). There is no fluent way to take part in the creation itself: to refuse a request (unsupported filter, index range, or encoding) with the proper status code, or to substitute a customMonitoredItemimplementation that pushes values directly instead of being sampled. Managers that need this keep overriding theCreateMonitoredItemAsyncfamily onAsyncCustomNodeManager.Where the limit is
src/Opc.Ua.Server/NodeManager/AsyncCustomNodeManager.cs:CreateMonitoredItemAsync(~6461) is the virtual that decides everything: it validates the filter, callsReadInitialValue(~6537), creates the sampled item, and only then callsOnMonitoredItemCreated(~6550). By the time the fluent hook fires, a refusal or substitution is no longer possible.src/Opc.Ua.Server/Fluent/FluentDelegates.cs:MonitoredItemCreatedHandlerreceives anISampledDataChangeMonitoredItem, i.e. it is post-creation by type.src/Opc.Ua.Server/Fluent/IFluentDispatcher.cs: no pre-creation route.NodeManagerTemplates.cs) does not overrideCreateMonitoredItemAsync, so a user partial can, but that is the pre-fluent pattern and it cannot be expressed per node.Who needs it (from the UA-.NETStandard-Samples audit)
Opc.Ua.Sample/MemoryBufferBadFilterNotAllowed), index ranges (BadIndexRangeInvalid) and encodings (BadDataEncodingUnsupported) at creation; creates its ownMemoryBufferMonitoredItem : MonitoredItemthat carries the node handle and is fed by the buffer's scan loop; overrides modify, delete and set-monitoring-mode to keep that item in sync. The sample tests assert those three status codes at creation, so the default sampled path (which accepts filters) cannot be usedWorkshop/PerfTestOnCreateMonitoredItemsCompleteand pushesDataValues straight intoIDataChangeMonitoredItem2.QueueValueevery 45 ms for half a register, deliberately bypassing sampling; that push is the point of the sampleWorkshop/AggregationCreateMonitoredItemsAsyncbecause the batch completion hook is synchronous and it needs to await the downstream subscriptionProposal
A pre-creation hook on the builder that can refuse or substitute, addressable per node, per type definition, or for a family of virtual nodes:
FluentNodeManagerBase(generated partial andFluentNodeManager) would overrideCreateMonitoredItemAsyncand consult the dispatcher before the default path runs.OnMonitoredItemModified/Deleted/OnMonitoringModeChanged).OnCreateMonitoredItemsCompleteAsync(the existingOnCreateMonitoredItemsCompleteis sync) would cover the Aggregation and PerfTest batch cases without a public-method override.IDataChangeMonitoredItem2(or anIValueUpdater-like handle bound to the item rather than to the node) from the decision would let PerfTest keep its direct-queue push under the fluent surface.Related: #4397 (on-demand nodes), #4398 (monitored-item lifecycle hooks).