Skip to content

Commit 253aaba

Browse files
Merge pull request #54 from engineering87/feature/background-event-processing
Feature/background event processing
2 parents 2912170 + 924af08 commit 253aaba

File tree

11 files changed

+278
-105
lines changed

11 files changed

+278
-105
lines changed

src/WART-Client/WART-Client.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
2525
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
2626
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
27-
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
27+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
2828
</ItemGroup>
2929

3030
</Project>

src/WART-Client/WartTestClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static async Task ConnectAsync(string wartHubUrl)
5353
}
5454
catch (Exception e)
5555
{
56-
Console.WriteLine(e);
56+
Console.WriteLine(e.Message);
5757
}
5858

5959
await Task.CompletedTask;

src/WART-Client/WartTestClientJwt.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static async Task ConnectAsync(string wartHubUrl, string key)
5858
}
5959
catch (Exception e)
6060
{
61-
Console.WriteLine(e);
61+
Console.WriteLine(e.Message);
6262
}
6363

6464
await Task.CompletedTask;

src/WART-Core/Authentication/JWT/JwtServiceCollectionExtension.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using Microsoft.Extensions.Logging;
1212
using Microsoft.IdentityModel.Tokens;
1313
using System.Linq;
14+
using WART_Core.Hubs;
15+
using WART_Core.Services;
1416

1517
namespace WART_Core.Authentication.JWT
1618
{
@@ -79,6 +81,12 @@ public static IServiceCollection AddJwtMiddleware(this IServiceCollection servic
7981
};
8082
});
8183

84+
// Register WART event queue as a singleton service.
85+
services.AddSingleton<WartEventQueueService>();
86+
87+
// Register the WART event worker as a hosted service.
88+
services.AddHostedService<WartEventWorker<WartHubJwt>>();
89+
8290
// Configure SignalR options, including error handling and timeouts
8391
services.AddSignalR(options =>
8492
{

src/WART-Core/Controllers/WartBaseController.cs

Lines changed: 8 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
using Microsoft.AspNetCore.Mvc;
55
using Microsoft.AspNetCore.SignalR;
66
using Microsoft.Extensions.Logging;
7-
using System;
8-
using System.Collections.Generic;
97
using System.Linq;
10-
using System.Threading.Tasks;
118
using WART_Core.Entity;
129
using WART_Core.Filters;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using WART_Core.Services;
1312

1413
namespace WART_Core.Controllers
1514
{
@@ -19,6 +18,8 @@ public abstract class WartBaseController<THub> : Controller where THub : Hub
1918
private readonly IHubContext<THub> _hubContext;
2019
private const string RouteDataKey = "REQUEST";
2120

21+
private WartEventQueueService _eventQueue;
22+
2223
protected WartBaseController(IHubContext<THub> hubContext, ILogger logger)
2324
{
2425
_hubContext = hubContext;
@@ -39,7 +40,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
3940
/// Processes the executed action and sends the event to the SignalR hub if applicable.
4041
/// </summary>
4142
/// <param name="context">The action executed context.</param>
42-
public override async void OnActionExecuted(ActionExecutedContext context)
43+
public override void OnActionExecuted(ActionExecutedContext context)
4344
{
4445
if (context?.Result is ObjectResult objectResult)
4546
{
@@ -52,107 +53,13 @@ public override async void OnActionExecuted(ActionExecutedContext context)
5253
var response = objectResult.Value;
5354

5455
var wartEvent = new WartEvent(request, response, httpMethod, httpPath, remoteAddress);
55-
await SendToHub(wartEvent, [.. context.Filters]);
56-
}
57-
}
58-
59-
base.OnActionExecuted(context);
60-
}
6156

62-
/// <summary>
63-
/// Sends the current event to the SignalR hub.
64-
/// </summary>
65-
/// <param name="wartEvent">The current WartEvent.</param>
66-
/// <param name="filters">The list of filters applied to the request.</param>
67-
private async Task SendToHub(WartEvent wartEvent, List<IFilterMetadata> filters)
68-
{
69-
try
70-
{
71-
// Retrieve the target groups based on the filters.
72-
var groups = GetTargetGroups(filters);
73-
74-
// If there are groups specified, send the event to each group in parallel.
75-
if (groups.Any())
76-
{
77-
// Sending events to multiple groups in parallel.
78-
var tasks = groups.Select(group => SendEventToGroup(wartEvent, group));
79-
await Task.WhenAll(tasks);
57+
_eventQueue = context.HttpContext?.RequestServices.GetService<WartEventQueueService>();
58+
_eventQueue?.Enqueue(new WartEventWithFilters(wartEvent, [.. context.Filters]));
8059
}
81-
else
82-
{
83-
// If no specific groups are defined, send the event to all connected clients.
84-
await SendEventToAllClients(wartEvent);
85-
}
86-
}
87-
catch (Exception ex)
88-
{
89-
_logger?.LogError(ex, "Error sending WartEvent to clients");
9060
}
91-
}
92-
93-
/// <summary>
94-
/// Retrieves the list of groups that the WartEvent should be sent to, based on the provided filters.
95-
/// </summary>
96-
/// <param name="filters">The list of filters that may contain group-related information.</param>
97-
/// <returns>A list of group names to send the WartEvent to.</returns>
98-
private IEnumerable<string> GetTargetGroups(List<IFilterMetadata> filters)
99-
{
100-
var groups = new List<string>();
10161

102-
// Check if there is a GroupWartAttribute filter indicating groups.
103-
if (filters.Any(f => f.GetType().Name == nameof(GroupWartAttribute)))
104-
{
105-
var wartGroup = filters.FirstOrDefault(f => f.GetType() == typeof(GroupWartAttribute)) as GroupWartAttribute;
106-
if (wartGroup != null)
107-
{
108-
groups.AddRange(wartGroup.GroupNames);
109-
}
110-
}
111-
112-
return groups;
113-
}
114-
115-
/// <summary>
116-
/// Sends the WartEvent to a specific group of clients.
117-
/// </summary>
118-
/// <param name="wartEvent">The WartEvent to send to the group.</param>
119-
/// <param name="group">The group name to which the event will be sent.</param>
120-
/// <returns>A Task representing the asynchronous operation.</returns>
121-
private async Task SendEventToGroup(WartEvent wartEvent, string group)
122-
{
123-
try
124-
{
125-
await _hubContext?.Clients
126-
.Group(group)
127-
.SendAsync("Send", wartEvent.ToString());
128-
129-
_logger?.LogInformation($"Group: {group}, WartEvent: {wartEvent}");
130-
}
131-
catch (Exception ex)
132-
{
133-
_logger?.LogError(ex, $"Error sending WartEvent to group {group}");
134-
}
135-
}
136-
137-
/// <summary>
138-
/// Sends the WartEvent to all connected clients.
139-
/// </summary>
140-
/// <param name="wartEvent">The WartEvent to send to all clients.</param>
141-
/// <returns>A Task representing the asynchronous operation.</returns>
142-
private async Task SendEventToAllClients(WartEvent wartEvent)
143-
{
144-
try
145-
{
146-
// Send the WartEvent to all connected clients using SignalR.
147-
await _hubContext?.Clients.All
148-
.SendAsync("Send", wartEvent.ToString());
149-
150-
_logger?.LogInformation("Event: {EventName}, Details: {EventDetails}", nameof(WartEvent), wartEvent.ToString());
151-
}
152-
catch (Exception ex)
153-
{
154-
_logger?.LogError(ex, "Error sending WartEvent to all clients");
155-
}
62+
base.OnActionExecuted(context);
15663
}
15764
}
15865
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// (c) 2024 Francesco Del Re <francesco.delre.87@gmail.com>
2+
// This code is licensed under MIT license (see LICENSE.txt for details)
3+
using Microsoft.AspNetCore.Mvc.Filters;
4+
using System.Collections.Generic;
5+
6+
namespace WART_Core.Entity
7+
{
8+
/// <summary>
9+
/// Represents an event that contains additional filter metadata.
10+
/// </summary>
11+
public class WartEventWithFilters
12+
{
13+
/// <summary>
14+
/// The main WartEvent object.
15+
/// </summary>
16+
public WartEvent WartEvent { get; set; }
17+
18+
/// <summary>
19+
/// A list of filters applied to the event.
20+
/// </summary>
21+
public List<IFilterMetadata> Filters { get; set; }
22+
23+
/// <summary>
24+
/// Initializes a new instance of the WartEventWithFilters class.
25+
/// </summary>
26+
/// <param name="wartEvent">The WartEvent to associate with the filters.</param>
27+
/// <param name="filters">The list of filters applied to the event.</param>
28+
public WartEventWithFilters(WartEvent wartEvent, List<IFilterMetadata> filters)
29+
{
30+
// Initialize the WartEvent and Filters properties
31+
WartEvent = wartEvent;
32+
Filters = filters;
33+
}
34+
}
35+
}

src/WART-Core/Hubs/WartHubBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,10 @@ public static int GetConnectionsCount()
122122
{
123123
return _connections.Count;
124124
}
125+
126+
/// <summary>
127+
/// Returns a value indicating whether there are connected clients.
128+
/// </summary>
129+
public static bool HasConnectedClients => !_connections.IsEmpty;
125130
}
126131
}

src/WART-Core/Middleware/WartServiceCollectionExtension.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.Linq;
1010
using WART_Core.Authentication.JWT;
1111
using WART_Core.Enum;
12+
using WART_Core.Hubs;
13+
using WART_Core.Services;
1214

1315
namespace WART_Core.Middleware
1416
{
@@ -32,6 +34,12 @@ public static IServiceCollection AddWartMiddleware(this IServiceCollection servi
3234
// Add console logging.
3335
services.AddLogging(configure => configure.AddConsole());
3436

37+
// Register WART event queue as a singleton service.
38+
services.AddSingleton<WartEventQueueService>();
39+
40+
// Register the WART event worker as a hosted service.
41+
services.AddHostedService<WartEventWorker<WartHub>>();
42+
3543
// Configure SignalR with custom options.
3644
services.AddSignalR(options =>
3745
{
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// (c) 2024 Francesco Del Re <francesco.delre.87@gmail.com>
2+
// This code is licensed under MIT license (see LICENSE.txt for details)
3+
using System.Collections.Concurrent;
4+
using WART_Core.Entity;
5+
6+
namespace WART_Core.Services
7+
{
8+
/// <summary>
9+
/// A service that manages a concurrent queue for WartEvent objects with filters.
10+
/// This class provides methods for enqueuing and dequeuing events.
11+
/// </summary>
12+
public class WartEventQueueService
13+
{
14+
// A thread-safe queue to hold WartEvent objects along with their associated filters.
15+
private readonly ConcurrentQueue<WartEventWithFilters> _queue = new ConcurrentQueue<WartEventWithFilters>();
16+
17+
/// <summary>
18+
/// Enqueues a WartEventWithFilters object to the queue.
19+
/// </summary>
20+
/// <param name="wartEventWithFilters">The WartEventWithFilters object to enqueue.</param>
21+
public void Enqueue(WartEventWithFilters wartEventWithFilters)
22+
{
23+
// Adds the event with filters to the concurrent queue.
24+
_queue.Enqueue(wartEventWithFilters);
25+
}
26+
27+
/// <summary>
28+
/// Attempts to dequeue a WartEventWithFilters object from the queue.
29+
/// </summary>
30+
/// <param name="wartEventWithFilters">The dequeued WartEventWithFilters object.</param>
31+
/// <returns>True if an event was dequeued; otherwise, false.</returns>
32+
public bool TryDequeue(out WartEventWithFilters wartEventWithFilters)
33+
{
34+
// Attempts to remove and return the event with filters from the queue.
35+
return _queue.TryDequeue(out wartEventWithFilters);
36+
}
37+
38+
/// <summary>
39+
/// Gets the current count of events in the queue.
40+
/// </summary>
41+
public int Count => _queue.Count;
42+
}
43+
}

0 commit comments

Comments
 (0)