public async Task Polling() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel.SessionId}'."); var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { NodeId = NodeId.Parse(VariableIds.Server_ServerStatus_CurrentTime), AttributeId = AttributeIds.Value } } }; for (int i = 0; i < 10; i++) { var readResult = await channel.ReadAsync(readRequest); logger.LogInformation("Read {0}", readResult.Results[0].GetValueOrDefault <DateTime>()); await Task.Delay(1000); } logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
public async Task Read() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel.SessionId}'."); var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { NodeId = NodeId.Parse(VariableIds.Server_ServerStatus), AttributeId = AttributeIds.Value } } }; var readResult = await channel.ReadAsync(readRequest); var serverStatus = readResult.Results[0].GetValueOrDefault <ServerStatusDataType>(); logger.LogInformation("Server status:"); logger.LogInformation(" ProductName: {0}", serverStatus.BuildInfo.ProductName); logger.LogInformation(" SoftwareVersion: {0}", serverStatus.BuildInfo.SoftwareVersion); logger.LogInformation(" ManufacturerName: {0}", serverStatus.BuildInfo.ManufacturerName); logger.LogInformation(" State: {0}", serverStatus.State); logger.LogInformation(" CurrentTime: {0}", serverStatus.CurrentTime); logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
private async Task <ClientSessionChannel> CreateChannelAsync(string endpointUrl, CancellationToken token = default) { try { this.logger?.LogTrace($"Begin creating {nameof(ClientSessionChannel)} for {endpointUrl}"); await this.CheckSuspension(token).ConfigureAwait(false); EndpointDescription endpoint; var mappedEndpoint = this.MappedEndpoints?.LastOrDefault(m => m.RequestedUrl == endpointUrl); if (mappedEndpoint?.Endpoint != null) { endpoint = mappedEndpoint.Endpoint; } else { endpoint = new EndpointDescription { EndpointUrl = endpointUrl }; } var channel = new ClientSessionChannel( this.LocalDescription, this.CertificateStore, this.UserIdentityProvider, endpoint, this.LoggerFactory, this.Options); channel.Faulted += (s, e) => { this.logger?.LogTrace($"Error creating {nameof(ClientSessionChannel)} for {endpointUrl}. OnFaulted"); var ch = (ClientSessionChannel)s !; try { ch.AbortAsync().Wait(); } catch { } }; channel.Closing += (s, e) => { this.logger?.LogTrace($"Removing {nameof(ClientSessionChannel)} for {endpointUrl} from channelMap."); this.channelMap.TryRemove(endpointUrl, out _); }; await channel.OpenAsync(token).ConfigureAwait(false); this.logger?.LogTrace($"Success creating {nameof(ClientSessionChannel)} for {endpointUrl}."); return(channel); } catch (Exception ex) { this.logger?.LogTrace($"Error creating {nameof(ClientSessionChannel)} for {endpointUrl}. {ex.Message}"); throw; } }
public async Task ConnnectToAllEndpoints() { // discover available endpoints of server. var getEndpointsRequest = new GetEndpointsRequest { EndpointUrl = EndpointUrl, ProfileUris = new[] { TransportProfileUris.UaTcpTransport } }; logger.LogInformation($"Discovering endpoints of '{getEndpointsRequest.EndpointUrl}'."); var getEndpointsResponse = await DiscoveryService.GetEndpointsAsync(getEndpointsRequest); // for each endpoint and user identity type, try creating a session and reading a few nodes. foreach (var selectedEndpoint in getEndpointsResponse.Endpoints .OrderBy(e => e.SecurityLevel)) { foreach (var selectedTokenPolicy in selectedEndpoint.UserIdentityTokens) { IUserIdentity selectedUserIdentity; switch (selectedTokenPolicy.TokenType) { case UserTokenType.Certificate: selectedUserIdentity = x509Identity; break; case UserTokenType.UserName: selectedUserIdentity = new UserNameIdentity("root", "secret"); break; case UserTokenType.Anonymous: selectedUserIdentity = new AnonymousIdentity(); break; default: continue; } var channel = new ClientSessionChannel( localDescription, certificateStore, selectedUserIdentity, selectedEndpoint, loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"UserIdentityToken: '{channel.UserIdentity}'."); logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); } } }
public async Task StructureTest() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, loggerFactory: loggerFactory); await channel.OpenAsync(); var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Structure") }, }, }; var readResponse = await channel.ReadAsync(readRequest); foreach (var result in readResponse.Results) { StatusCode.IsGood(result.StatusCode) .Should().BeTrue(); } // reading this node returns an array of ExtensionObjects var obj = readResponse.Results[0].Value; // create new DataValue for writing. Most servers reject writing values with timestamps. var newValue = new DataValue(obj); var writeRequest = new WriteRequest { NodesToWrite = new[] { new WriteValue { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Structure"), Value = newValue }, }, }; var writeResponse = await channel.WriteAsync(writeRequest); foreach (var result in writeResponse.Results) { StatusCode.IsGood(result) .Should().BeTrue(); } logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
public async Task BrowseObjects() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, SecurityPolicyUris.None, loggerFactory: loggerFactory); await channel.OpenAsync(); var rds = new List <ReferenceDescription>(); var browseRequest = new BrowseRequest { NodesToBrowse = new[] { new BrowseDescription { NodeId = ExpandedNodeId.ToNodeId(ExpandedNodeId.Parse(ObjectIds.ObjectsFolder), channel.NamespaceUris), ReferenceTypeId = NodeId.Parse(ReferenceTypeIds.HierarchicalReferences), ResultMask = (uint)BrowseResultMask.TargetInfo, NodeClassMask = (uint)NodeClass.Unspecified, BrowseDirection = BrowseDirection.Forward, IncludeSubtypes = true } }, RequestedMaxReferencesPerNode = 1000 }; var browseResponse = await channel.BrowseAsync(browseRequest).ConfigureAwait(false); rds.AddRange(browseResponse.Results.Where(result => result.References != null).SelectMany(result => result.References)); var continuationPoints = browseResponse.Results.Select(br => br.ContinuationPoint).Where(cp => cp != null).ToArray(); while (continuationPoints.Length > 0) { var browseNextRequest = new BrowseNextRequest { ContinuationPoints = continuationPoints, ReleaseContinuationPoints = false }; var browseNextResponse = await channel.BrowseNextAsync(browseNextRequest); rds.AddRange(browseNextResponse.Results.Where(result => result.References != null).SelectMany(result => result.References)); continuationPoints = browseNextResponse.Results.Select(br => br.ContinuationPoint).Where(cp => cp != null).ToArray(); } rds .Should().NotBeEmpty(); logger.LogInformation("+ Objects, 0:Objects, Object"); foreach (var rd in rds) { logger.LogInformation(" + {0}, {1}, {2}", rd.DisplayName, rd.BrowseName, rd.NodeClass); } logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
public async Task CustomVectorAdd() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), "opc.tcp://*****:*****@" ------------------"); logger.LogInformation($" {result}"); logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); result.Z .Should().Be(6.0); }
/// <summary> /// Signals the channel state is Closing. /// </summary> /// <param name="channel">The session channel. </param> /// <param name="token">A cancellation token. </param> /// <returns>A task.</returns> private async Task WhenChannelClosingAsync(ClientSessionChannel channel, CancellationToken token = default) { var tcs = new TaskCompletionSource <bool>(); EventHandler handler = (o, e) => { tcs.TrySetResult(true); }; using (token.Register(state => ((TaskCompletionSource <bool>)state !).TrySetCanceled(), tcs, false)) { try { channel.Closing += handler; if (channel.State == CommunicationState.Opened) { await tcs.Task; } } finally { channel.Closing -= handler; } } }
public async Task TransferSubscription() { var channel1 = new ClientSessionChannel( localDescription, certificateStore, new UserNameIdentity("root", "secret"), EndpointUrl, loggerFactory: loggerFactory); await channel1.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel1.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel1.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel1.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel1.SessionId}'."); // create the keep alive subscription. var subscriptionRequest = new CreateSubscriptionRequest { RequestedPublishingInterval = 1000f, RequestedMaxKeepAliveCount = 30, RequestedLifetimeCount = 30 * 3, PublishingEnabled = true, }; var subscriptionResponse = await channel1.CreateSubscriptionAsync(subscriptionRequest).ConfigureAwait(false); var id = subscriptionResponse.SubscriptionId; void onPublish(PublishResponse pr) { // loop thru all the data change notifications and log them. var dcns = pr.NotificationMessage.NotificationData.OfType <DataChangeNotification>(); foreach (var dcn in dcns) { foreach (var min in dcn.MonitoredItems) { logger.LogInformation($"sub: {pr.SubscriptionId}; handle: {min.ClientHandle}; value: {min.Value}"); } } } void onPublishError(Exception ex) { logger.LogInformation("Exception in publish response handler: {0}", ex.GetBaseException().Message); } var token = channel1 .Where(pr => pr.SubscriptionId == id) .Subscribe(onPublish, onPublishError); var itemsRequest = new CreateMonitoredItemsRequest { SubscriptionId = id, ItemsToCreate = new MonitoredItemCreateRequest[] { new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { NodeId = NodeId.Parse("i=2258"), AttributeId = AttributeIds.Value }, MonitoringMode = MonitoringMode.Reporting, RequestedParameters = new MonitoringParameters { ClientHandle = 12345, SamplingInterval = -1, QueueSize = 0, DiscardOldest = true } } }, }; var itemsResponse = await channel1.CreateMonitoredItemsAsync(itemsRequest); await Task.Delay(3000); var channel2 = new ClientSessionChannel( localDescription, certificateStore, new UserNameIdentity("root", "secret"), EndpointUrl); await channel2.OpenAsync(); var token2 = channel2 .Where(pr => pr.SubscriptionId == id) .Subscribe(onPublish, onPublishError); var transferRequest = new TransferSubscriptionsRequest { SubscriptionIds = new[] { id }, SendInitialValues = true }; var transferResult = await channel2.TransferSubscriptionsAsync(transferRequest); StatusCode.IsGood(transferResult.Results[0].StatusCode) .Should().BeTrue(); logger.LogInformation($"Transfered subscriptions to new client."); await Task.Delay(3000); logger.LogInformation($"Closing session '{channel1.SessionId}'."); await channel1.CloseAsync(); logger.LogInformation($"Closing session '{channel2.SessionId}'."); await channel2.CloseAsync(); }
public async Task StackTest() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel.SessionId}'."); var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Boolean") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.SByte") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Int16") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Int32") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Int64") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Byte") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.UInt16") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.UInt32") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.UInt64") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Float") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Double") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.String") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.DateTime") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.Guid") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.ByteString") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.XmlElement") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.LocalizedText") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Scalar.QualifiedName") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Boolean") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.SByte") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Int16") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Int32") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Int64") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Byte") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.UInt16") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.UInt32") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.UInt64") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Float") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Double") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.String") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.DateTime") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.Guid") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.ByteString") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.XmlElement") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.LocalizedText") }, new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse("ns=2;s=Demo.Static.Arrays.QualifiedName") }, }, }; var sw = new Stopwatch(); sw.Restart(); for (int i = 0; i < 1; i++) { var readResponse = await channel.ReadAsync(readRequest); foreach (var result in readResponse.Results) { StatusCode.IsGood(result.StatusCode) .Should().BeTrue(); var obj = result.GetValue(); } } sw.Stop(); logger.LogInformation($"{sw.ElapsedMilliseconds} ms"); logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
public async Task ReadHistorical() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), "opc.tcp://localhost:48010", loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel.SessionId}'."); var historyReadRequest = new HistoryReadRequest { HistoryReadDetails = new ReadRawModifiedDetails { StartTime = DateTime.UtcNow - TimeSpan.FromMinutes(10), EndTime = DateTime.UtcNow, ReturnBounds = true, IsReadModified = false }, NodesToRead = new[] { new HistoryReadValueId { NodeId = NodeId.Parse("ns=2;s=Demo.History.DoubleWithHistory") } }, }; var historyReadResponse = await channel.HistoryReadAsync(historyReadRequest); var result = historyReadResponse.Results[0]; StatusCode.IsGood(result.StatusCode) .Should().BeTrue(); logger.LogInformation($"HistoryRead response status code: {result.StatusCode}, HistoryData count: {((HistoryData)result.HistoryData).DataValues.Length}."); #if false // UaCPPserver does not appear to store event history. { var historyReadRequest2 = new HistoryReadRequest { HistoryReadDetails = new ReadEventDetails { StartTime = DateTime.UtcNow - TimeSpan.FromMinutes(10), EndTime = DateTime.UtcNow, Filter = new EventFilter // Use EventHelper to select all the fields of AlarmCondition. { SelectClauses = EventHelper.GetSelectClauses <AlarmCondition>() } }, NodesToRead = new[] { new HistoryReadValueId { NodeId = NodeId.Parse("ns=2;s=Demo.History.DoubleWithHistory") } }, }; var historyReadResponse2 = await channel.HistoryReadAsync(historyReadRequest2); var result2 = historyReadResponse2.Results[0]; StatusCode.IsGood(result2.StatusCode) .Should().BeTrue(); logger.LogInformation($"HistoryRead response status code: {result2.StatusCode}, HistoryEvent count: {((HistoryEvent)result2.HistoryData).Events.Length}."); // Use EventHelper to create AlarmConditions from the HistoryEventFieldList var alarms = ((HistoryEvent)result2.HistoryData).Events.Select(e => EventHelper.Deserialize <AlarmCondition>(e.EventFields)); } #endif logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); }
public async Task TestSubscription() { var channel = new ClientSessionChannel( localDescription, certificateStore, new AnonymousIdentity(), EndpointUrl, loggerFactory: loggerFactory); await channel.OpenAsync(); logger.LogInformation($"Opened session with endpoint '{channel.RemoteEndpoint.EndpointUrl}'."); logger.LogInformation($"SecurityPolicy: '{channel.RemoteEndpoint.SecurityPolicyUri}'."); logger.LogInformation($"SecurityMode: '{channel.RemoteEndpoint.SecurityMode}'."); logger.LogInformation($"Activated session '{channel.SessionId}'."); var req = new CreateSubscriptionRequest { RequestedPublishingInterval = 1000.0, RequestedMaxKeepAliveCount = 30, RequestedLifetimeCount = 30 * 3, PublishingEnabled = true, }; var res = await channel.CreateSubscriptionAsync(req); var id = res.SubscriptionId; logger.LogInformation($"Created subscription '{id}'."); var req2 = new CreateMonitoredItemsRequest { SubscriptionId = id, TimestampsToReturn = TimestampsToReturn.Both, ItemsToCreate = new MonitoredItemCreateRequest[] { new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { AttributeId = AttributeIds.Value, NodeId = NodeId.Parse(VariableIds.Server_ServerStatus_CurrentTime) }, MonitoringMode = MonitoringMode.Reporting, RequestedParameters = new MonitoringParameters { ClientHandle = 42, QueueSize = 2, DiscardOldest = true, SamplingInterval = 500.0 }, }, }, }; var res2 = await channel.CreateMonitoredItemsAsync(req2); logger.LogInformation("Subscribe to PublishResponse stream."); var numOfResponses = 0; void onPublish(PublishResponse pr) { numOfResponses++; // loop thru all the data change notifications and log them. var dcns = pr.NotificationMessage.NotificationData.OfType <DataChangeNotification>(); foreach (var dcn in dcns) { foreach (var min in dcn.MonitoredItems) { logger.LogInformation($"sub: {pr.SubscriptionId}; handle: {min.ClientHandle}; value: {min.Value}"); } } } void onPublishError(Exception ex) { logger.LogInformation("Exception in publish response handler: {0}", ex.GetBaseException().Message); } var token = channel .Where(pr => pr.SubscriptionId == id) .Subscribe(onPublish, onPublishError); await Task.Delay(5000); logger.LogInformation($"Closing session '{channel.SessionId}'."); await channel.CloseAsync(); numOfResponses .Should().BeGreaterThan(0); }