Skip to content

Commit 00dd829

Browse files
authored
Expose dedicated SignalRGroupConnection model for presence API (#2217)
Expose a dedicated model class `SignalRGroupConnection` instead of `GroupMember` from SignalR service protocol.
1 parent f1afbf8 commit 00dd829

File tree

19 files changed

+157
-44
lines changed

19 files changed

+157
-44
lines changed

src/Microsoft.Azure.SignalR.AspNet/ServerConnections/ServiceConnectionManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public Task StopGetServersPing()
143143
return Task.WhenAll(GetConnections().Select(s => s.StopGetServersPing()));
144144
}
145145

146-
public IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, CancellationToken token = default)
146+
public IAsyncEnumerable<Page<SignalRGroupConnection>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, CancellationToken token = default)
147147
{
148148
throw new NotImplementedException();
149149
}

src/Microsoft.Azure.SignalR.Common/Interfaces/IServiceMessageWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ internal interface IServiceMessageWriter
1919

2020
Task<bool> WriteAckableMessageAsync(ServiceMessage serviceMessage, CancellationToken cancellationToken = default);
2121

22-
IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string? continuationToken = null, ulong? tracingId = null, CancellationToken token = default);
22+
IAsyncEnumerable<Page<SignalRGroupConnection>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string? continuationToken = null, ulong? tracingId = null, CancellationToken token = default);
2323
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Microsoft.Azure.SignalR;
7+
8+
#nullable enable
9+
10+
// TODO: make public later
11+
internal sealed record SignalRGroupConnection
12+
{
13+
[JsonPropertyName("connectionId")]
14+
public string ConnectionId { internal set; get; }
15+
16+
[JsonPropertyName("userId")]
17+
public string? UserId { get; internal set; }
18+
19+
public SignalRGroupConnection(string connectionId, string? userId = default)
20+
{
21+
ConnectionId = connectionId;
22+
UserId = userId;
23+
}
24+
}

src/Microsoft.Azure.SignalR.Common/Pagination/GroupMemberQueryResultPage.cs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,27 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
68

79
using Azure;
810

9-
using Microsoft.Azure.SignalR.Protocol;
10-
1111
#nullable enable
1212

1313
namespace Microsoft.Azure.SignalR;
1414

15-
internal class GroupMemberQueryResultPage : Page<GroupMember>
15+
[JsonConverter(typeof(GroupMemberQueryResultPageConverter))]
16+
internal class GroupMemberQueryResultPage : Page<SignalRGroupConnection>
1617
{
17-
private readonly IReadOnlyList<GroupMember> _value;
18-
private readonly string? _continuationToken;
19-
20-
public GroupMemberQueryResultPage(IReadOnlyList<GroupMember> value, string? continuationToken)
18+
public GroupMemberQueryResultPage(IReadOnlyList<SignalRGroupConnection> values, string? continuationToken)
2119
{
22-
_value = value;
23-
_continuationToken = continuationToken;
20+
Values = values ?? throw new ArgumentNullException(nameof(values));
21+
ContinuationToken = continuationToken;
2422
}
2523

26-
public override IReadOnlyList<GroupMember> Values => _value;
24+
public override IReadOnlyList<SignalRGroupConnection> Values { get; }
2725

28-
public override string? ContinuationToken => _continuationToken;
26+
public override string? ContinuationToken { get; }
2927

3028
public override Response GetRawResponse()
3129
{
@@ -34,3 +32,58 @@ public override Response GetRawResponse()
3432
throw new NotSupportedException();
3533
}
3634
}
35+
36+
internal class GroupMemberQueryResultPageConverter : JsonConverter<GroupMemberQueryResultPage>
37+
{
38+
public override GroupMemberQueryResultPage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
39+
{
40+
if (reader.TokenType != JsonTokenType.StartObject)
41+
{
42+
throw new JsonException("Expected StartObject token");
43+
}
44+
45+
IReadOnlyList<SignalRGroupConnection>? values = null;
46+
string? continuationToken = null;
47+
48+
while (reader.Read())
49+
{
50+
if (reader.TokenType == JsonTokenType.EndObject)
51+
{
52+
break;
53+
}
54+
55+
if (reader.TokenType != JsonTokenType.PropertyName)
56+
{
57+
throw new JsonException("Expected PropertyName token");
58+
}
59+
60+
string propertyName = reader.GetString()!;
61+
reader.Read();
62+
63+
switch (propertyName)
64+
{
65+
case string s when s.Equals("value", StringComparison.OrdinalIgnoreCase):
66+
values = JsonSerializer.Deserialize<List<SignalRGroupConnection>>(ref reader, options);
67+
break;
68+
case string s when s.Equals("nextlink", StringComparison.OrdinalIgnoreCase):
69+
continuationToken = JsonSerializer.Deserialize<string>(ref reader, options);
70+
break;
71+
default:
72+
reader.Skip();
73+
break;
74+
}
75+
}
76+
77+
return new GroupMemberQueryResultPage(values ?? new List<SignalRGroupConnection>(), continuationToken);
78+
}
79+
80+
public override void Write(Utf8JsonWriter writer, GroupMemberQueryResultPage value, JsonSerializerOptions options)
81+
{
82+
writer.WriteStartObject();
83+
writer.WritePropertyName("value");
84+
JsonSerializer.Serialize(writer, value.Values, options);
85+
writer.WritePropertyName("nextLink");
86+
JsonSerializer.Serialize(writer, value.ContinuationToken, options);
87+
writer.WriteEndObject();
88+
}
89+
}

src/Microsoft.Azure.SignalR.Common/Pagination/PagenableGroupMember.cs renamed to src/Microsoft.Azure.SignalR.Common/Pagination/PageableGroupMember.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,21 @@
77

88
using Azure;
99

10-
using Microsoft.Azure.SignalR.Protocol;
11-
1210
namespace Microsoft.Azure.SignalR;
1311

1412
#nullable enable
1513

16-
internal class PagenableGroupMember : AsyncPageable<GroupMember>
14+
internal class PageableGroupMember : AsyncPageable<SignalRGroupConnection>
1715
{
1816
// (string? continuationToken, int? pageSizeHint) => IAsyncEnumerable<Page<GroupMember>>
19-
private readonly Func<string?, int?, IAsyncEnumerable<Page<GroupMember>>> _fetchPages;
17+
private readonly Func<string?, int?, IAsyncEnumerable<Page<SignalRGroupConnection>>> _fetchPages;
2018

21-
public PagenableGroupMember(Func<string?, int?, IAsyncEnumerable<Page<GroupMember>>> fetchPages, CancellationToken cancellationToken = default): base(cancellationToken)
19+
public PageableGroupMember(Func<string?, int?, IAsyncEnumerable<Page<SignalRGroupConnection>>> fetchPages, CancellationToken cancellationToken = default) : base(cancellationToken)
2220
{
2321
_fetchPages = fetchPages;
2422
}
2523

26-
public override IAsyncEnumerable<Page<GroupMember>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
24+
public override IAsyncEnumerable<Page<SignalRGroupConnection>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
2725
{
2826
return _fetchPages(continuationToken, pageSizeHint);
2927
}

src/Microsoft.Azure.SignalR.Common/ServiceConnections/MultiEndpointMessageWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ private async Task WriteSingleEndpointMessageAsync(HubServiceEndpoint endpoint,
176176
}
177177
}
178178

179-
public async IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, [EnumeratorCancellation] CancellationToken token = default)
179+
public async IAsyncEnumerable<Page<SignalRGroupConnection>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, [EnumeratorCancellation] CancellationToken token = default)
180180
{
181181
if (TargetEndpoints.Length == 0)
182182
{

src/Microsoft.Azure.SignalR.Common/ServiceConnections/MultiEndpointServiceConnectionContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public Task<bool> WriteAckableMessageAsync(ServiceMessage serviceMessage, Cancel
157157
return CreateMessageWriter(serviceMessage).WriteAckableMessageAsync(serviceMessage, cancellationToken);
158158
}
159159

160-
public IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, CancellationToken token = default)
160+
public IAsyncEnumerable<Page<SignalRGroupConnection>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, CancellationToken token = default)
161161
{
162162
var targetEndpoints = _routerEndpoints.needRouter ? _router.GetEndpointsForGroup(groupName, _routerEndpoints.endpoints) : _routerEndpoints.endpoints;
163163
var messageWriter = new MultiEndpointMessageWriter(targetEndpoints?.ToList(), _loggerFactory);

src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionContainerBase.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public async Task<bool> WriteAckableMessageAsync(ServiceMessage serviceMessage,
252252
return AckHandler.HandleAckStatus(ackableMessage, status);
253253
}
254254

255-
public async IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, [EnumeratorCancellation] CancellationToken token = default)
255+
public async IAsyncEnumerable<Page<SignalRGroupConnection>> ListConnectionsInGroupAsync(string groupName, int? top = null, int? maxPageSize = null, string continuationToken = null, ulong? tracingId = null, [EnumeratorCancellation] CancellationToken token = default)
256256
{
257257
if (string.IsNullOrWhiteSpace(groupName))
258258
{
@@ -273,7 +273,7 @@ public async IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(str
273273
do
274274
{
275275
var response = await InvokeAsync<GroupMemberQueryResponse>(message, token);
276-
yield return new GroupMemberQueryResultPage(response.Members.ToList(), response.ContinuationToken);
276+
yield return new GroupMemberQueryResultPage([.. response.Members.Select(m => new SignalRGroupConnection(m.ConnectionId, m.UserId))], response.ContinuationToken);
277277

278278
if (response.ContinuationToken == null)
279279
{
@@ -282,6 +282,10 @@ public async IAsyncEnumerable<Page<GroupMember>> ListConnectionsInGroupAsync(str
282282
if (message.Top != null)
283283
{
284284
message.Top -= response.Members.Count;
285+
if (message.Top <= 0)
286+
{
287+
yield break;
288+
}
285289
}
286290
message.ContinuationToken = response.ContinuationToken;
287291
} while (true);

src/Microsoft.Azure.SignalR.Management/HubContext/GroupManager.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Azure;
99

1010
using Microsoft.AspNetCore.SignalR;
11-
using Microsoft.Azure.SignalR.Protocol;
1211

1312
namespace Microsoft.Azure.SignalR.Management
1413
{
@@ -50,6 +49,6 @@ public abstract class GroupManager : IGroupManager
5049
/// <param name="top">The maximum number of connections to return.</param>
5150
/// <param name="cancellationToken">The cancellation token.</param>
5251
/// <returns>An asynchronous enumerable of group members.</returns>
53-
internal virtual AsyncPageable<GroupMember> ListConnectionsInGroup(string groupName, int? top = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
52+
internal virtual AsyncPageable<SignalRGroupConnection> ListConnectionsInGroup(string groupName, int? top = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
5453
}
5554
}

src/Microsoft.Azure.SignalR.Management/HubContext/GroupManagerAdapter.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
using Azure;
99

10-
using Microsoft.Azure.SignalR.Protocol;
11-
1210
namespace Microsoft.Azure.SignalR.Management
1311
{
1412
internal class GroupManagerAdapter : GroupManager
@@ -26,7 +24,7 @@ public GroupManagerAdapter(IServiceHubLifetimeManager lifetimeManager)
2624

2725
public override Task RemoveFromAllGroupsAsync(string connectionId, CancellationToken cancellationToken = default) => _lifetimeManager.RemoveFromAllGroupsAsync(connectionId, cancellationToken);
2826

29-
internal override AsyncPageable<GroupMember> ListConnectionsInGroup(string groupName, int? top = null, CancellationToken cancellationToken = default)
27+
internal override AsyncPageable<SignalRGroupConnection> ListConnectionsInGroup(string groupName, int? top = null, CancellationToken cancellationToken = default)
3028
{
3129
if (string.IsNullOrWhiteSpace(groupName))
3230
{

0 commit comments

Comments
 (0)