Skip to content

Commit dea92ee

Browse files
authored
Merge pull request #32 from sebastiano1972/master
Made client properties thread safe.
2 parents 39d4b97 + 496982c commit dea92ee

File tree

4 files changed

+219
-12
lines changed

4 files changed

+219
-12
lines changed

Lib.AspNetCore.ServerSentEvents/IServerSentEventsClient.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Threading;
43
using System.Threading.Tasks;
54
using System.Security.Claims;
@@ -26,11 +25,6 @@ public interface IServerSentEventsClient
2625
/// Gets the value indicating if client is connected.
2726
/// </summary>
2827
bool IsConnected { get; }
29-
30-
/// <summary>
31-
/// A set of key-values pairs to store pieces of information that can be used to select clients when sending events.
32-
/// </summary>
33-
IDictionary<string, string> Properties { get; }
3428
#endregion
3529

3630
#region Methods
@@ -63,6 +57,31 @@ public interface IServerSentEventsClient
6357
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
6458
/// <returns>The task object representing the asynchronous operation.</returns>
6559
Task SendEventAsync(ServerSentEvent serverSentEvent, CancellationToken cancellationToken);
60+
61+
/// <summary>
62+
/// Retrieves a piece of information associated to this client.
63+
/// </summary>
64+
/// <typeparam name="T">The type of the property being retrieved.</typeparam>
65+
/// <param name="name">The name of the property being retrieved.</param>
66+
/// <returns>The value of the property whose name has been specified if it exists in the set of properties associated to the client. Default otherwise.</returns>
67+
T GetProperty<T>(string name);
68+
69+
/// <summary>
70+
/// Removes a piece of information associated to this client.
71+
/// </summary>
72+
/// <typeparam name="T">The type of the property being removed.</typeparam>
73+
/// <param name="name">The name of the property being removed.</param>
74+
/// <returns>The value of the property whose name has been specified if it exists in the set of properties associated to the client. Default otherwise.</returns>
75+
T RemoveProperty<T>(string name);
76+
77+
/// <summary>
78+
/// Adds a property to the client so that it can be used to store client related pieces of information.
79+
/// </summary>
80+
/// <param name="name">The name of the property being added.</param>
81+
/// <param name="value">The value of the property being added.</param>
82+
/// <param name="overwrite">When true and the property already exists, its value will be updated. When false and the property already exists, its value will not be updated.</param>
83+
/// <returns>True if the property has been added or updated, false otherwise.</returns>
84+
bool SetProperty(string name, object value, bool overwrite = false);
6685
#endregion
6786
}
6887
}

Lib.AspNetCore.ServerSentEvents/Lib.AspNetCore.ServerSentEvents.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@
3333
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
3434
<FrameworkReference Include="Microsoft.AspNetCore.App" />
3535
</ItemGroup>
36+
<ItemGroup>
37+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
38+
<_Parameter1>Test.AspNetCore.ServerSentEvents</_Parameter1>
39+
</AssemblyAttribute>
40+
</ItemGroup>
3641
</Project>

Lib.AspNetCore.ServerSentEvents/ServerSentEventsClient.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Collections.Concurrent;
33
using System.Threading;
44
using System.Threading.Tasks;
55
using System.Security.Claims;
@@ -14,6 +14,7 @@ public sealed class ServerSentEventsClient : IServerSentEventsClient
1414
{
1515
#region Fields
1616
private readonly HttpResponse _response;
17+
private readonly ConcurrentDictionary<string, object> _properties = new ConcurrentDictionary<string, object>();
1718
#endregion
1819

1920
#region Properties
@@ -31,11 +32,6 @@ public sealed class ServerSentEventsClient : IServerSentEventsClient
3132
/// Gets the value indicating if client is connected.
3233
/// </summary>
3334
public bool IsConnected { get; internal set; }
34-
35-
/// <summary>
36-
/// A set of key-values pairs to store pieces of information that can be used to select clients when sending events.
37-
/// </summary>
38-
public IDictionary<string, string> Properties { get; } = new Dictionary<string, string>();
3935
#endregion
4036

4137
#region Constructor
@@ -50,6 +46,38 @@ internal ServerSentEventsClient(Guid id, ClaimsPrincipal user, HttpResponse resp
5046
#endregion
5147

5248
#region Methods
49+
/// <summary>
50+
/// Retrieves a piece of information associated to this client. This method is thread safe.
51+
/// </summary>
52+
/// <typeparam name="T">The type of the property being retrieved.</typeparam>
53+
/// <param name="name">The name of the property being retrieved.</param>
54+
/// <returns>The value of the property whose name has been specified if it exists in the set of properties associated to the client. Default otherwise.</returns>
55+
public T GetProperty<T>(string name)
56+
{
57+
if (_properties.TryGetValue(name, out var value))
58+
{
59+
return (T) value;
60+
}
61+
62+
return default;
63+
}
64+
65+
/// <summary>
66+
/// Removes a piece of information associated to this client. This method is thread safe.
67+
/// </summary>
68+
/// <typeparam name="T">The type of the property being removed.</typeparam>
69+
/// <param name="name">The name of the property being removed.</param>
70+
/// <returns>The value of the property whose name has been specified if it exists in the set of properties associated to the client. Default otherwise.</returns>
71+
public T RemoveProperty<T>(string name)
72+
{
73+
if (_properties.TryRemove(name, out var value))
74+
{
75+
return (T)value;
76+
}
77+
78+
return default;
79+
}
80+
5381
/// <summary>
5482
/// Sends event to client.
5583
/// </summary>
@@ -92,6 +120,24 @@ public Task SendEventAsync(ServerSentEvent serverSentEvent, CancellationToken ca
92120
return SendAsync(ServerSentEventsHelper.GetEventBytes(serverSentEvent), cancellationToken);
93121
}
94122

123+
/// <summary>
124+
/// Adds a property to the client so that it can be used to store client related pieces of information. This method is thread safe.
125+
/// </summary>
126+
/// <param name="name">The name of the property being added.</param>
127+
/// <param name="value">The value of the property being added.</param>
128+
/// <param name="overwrite">When true and the property already exists, its value will be updated. When false and the property already exists, its value will not be updated.</param>
129+
/// <returns>True if the property has been added or updated, false otherwise.</returns>
130+
public bool SetProperty(string name, object value, bool overwrite = false)
131+
{
132+
if (overwrite)
133+
{
134+
_properties.AddOrUpdate(name, value, (k, v) => value);
135+
return true;
136+
}
137+
138+
return _properties.TryAdd(name, value);
139+
}
140+
95141
internal Task SendAsync(ServerSentEventBytes serverSentEvent, CancellationToken cancellationToken)
96142
{
97143
CheckIsConnected();
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System;
2+
using System.Security.Claims;
3+
using Lib.AspNetCore.ServerSentEvents.Internals;
4+
using Microsoft.AspNetCore.Http;
5+
using Xunit;
6+
7+
namespace Test.AspNetCore.ServerSentEvents
8+
{
9+
public class ServerSentEventsClientTests
10+
{
11+
#region Fields
12+
private const string PROPERTY_1_NAME = "property-1";
13+
private const string PROPERTY_1_VALUE = "property-1-value";
14+
private const string PROPERTY_1_UPDATED_VALUE = "property-1-updated-value";
15+
#endregion
16+
17+
#region Prepare SUT
18+
private static ServerSentEventsClient PrepareServerSentEventsClient()
19+
{
20+
var context = new DefaultHttpContext();
21+
return new ServerSentEventsClient(Guid.NewGuid(), new ClaimsPrincipal(), context.Response);
22+
}
23+
#endregion
24+
25+
#region Tests
26+
[Fact]
27+
public void GetProperty_ReturnsTheStoredValue()
28+
{
29+
// ARRANGE
30+
var client = PrepareServerSentEventsClient();
31+
32+
client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE);
33+
34+
// ACT
35+
var expected = client.GetProperty<string>(PROPERTY_1_NAME);
36+
37+
// ASSERT
38+
Assert.Equal(expected, PROPERTY_1_VALUE);
39+
}
40+
41+
[Fact]
42+
public void GetProperty_ReturnsDefaultIfPropertyNotPresent()
43+
{
44+
// ARRANGE
45+
var client = PrepareServerSentEventsClient();
46+
47+
client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE);
48+
49+
// ACT
50+
var actual = client.GetProperty<string>(PROPERTY_1_UPDATED_VALUE);
51+
52+
// ASSERT
53+
Assert.Equal(default, actual);
54+
}
55+
56+
[Fact]
57+
public void RemoveProperty_RemovesTheProperty()
58+
{
59+
// ARRANGE
60+
var client = PrepareServerSentEventsClient();
61+
62+
client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE);
63+
64+
// ACT
65+
var actual = client.RemoveProperty<string>(PROPERTY_1_NAME);
66+
67+
// ASSERT
68+
Assert.Equal(PROPERTY_1_VALUE, actual);
69+
}
70+
71+
[Fact]
72+
public void RemoveProperty_DoesNotBreakIfPropertyDoesNotExist()
73+
{
74+
// ARRANGE
75+
var client = PrepareServerSentEventsClient();
76+
77+
// ACT
78+
var actual = client.RemoveProperty<string>(PROPERTY_1_NAME);
79+
80+
// ASSERT
81+
Assert.Equal(default, actual);
82+
}
83+
84+
[Fact]
85+
public void SetProperty_AddProperty()
86+
{
87+
// ARRANGE
88+
var client = PrepareServerSentEventsClient();
89+
90+
// ACT
91+
var actual = client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE, true);
92+
93+
// ASSERT
94+
var actualValue = client.GetProperty<string>(PROPERTY_1_NAME);
95+
96+
Assert.True(actual);
97+
Assert.Equal(PROPERTY_1_VALUE, actualValue);
98+
}
99+
100+
[Fact]
101+
public void SetProperty_UpdateProperty()
102+
{
103+
// ARRANGE
104+
var client = PrepareServerSentEventsClient();
105+
106+
client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE);
107+
108+
// ACT
109+
var actual = client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_UPDATED_VALUE, true);
110+
111+
// ASSERT
112+
var actualValue = client.GetProperty<string>(PROPERTY_1_NAME);
113+
114+
Assert.True(actual);
115+
Assert.Equal(PROPERTY_1_UPDATED_VALUE, actualValue);
116+
}
117+
118+
[Fact]
119+
public void SetProperty_DoesNotUpdateProperty()
120+
{
121+
// ARRANGE
122+
var client = PrepareServerSentEventsClient();
123+
124+
client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_VALUE);
125+
126+
// ACT
127+
var actual = client.SetProperty(PROPERTY_1_NAME, PROPERTY_1_UPDATED_VALUE);
128+
129+
// ASSERT
130+
var actualValue = client.GetProperty<string>(PROPERTY_1_NAME);
131+
132+
Assert.False(actual);
133+
Assert.Equal(PROPERTY_1_VALUE, actualValue);
134+
}
135+
#endregion
136+
}
137+
}

0 commit comments

Comments
 (0)