Skip to content

Commit 5005dc1

Browse files
committed
Moving requesting push message delivery to background. Resolves #4
1 parent d1c5444 commit 5005dc1

File tree

11 files changed

+152
-15
lines changed

11 files changed

+152
-15
lines changed

Demo.AspNetCore.PushNotifications.Services.Abstractions/IPushNotificationService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Threading.Tasks;
1+
using System.Threading;
2+
using System.Threading.Tasks;
23
using Lib.Net.Http.WebPush;
34

45
namespace Demo.AspNetCore.PushNotifications.Services.Abstractions
@@ -8,5 +9,7 @@ public interface IPushNotificationService
89
string PublicKey { get; }
910

1011
Task SendNotificationAsync(PushSubscription subscription, PushMessage message);
12+
13+
Task SendNotificationAsync(PushSubscription subscription, PushMessage message, CancellationToken cancellationToken);
1114
}
1215
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using Lib.Net.Http.WebPush;
4+
5+
namespace Demo.AspNetCore.PushNotifications.Services.Abstractions
6+
{
7+
public interface IPushNotificationsQueue
8+
{
9+
void Enqueue(PushMessage message);
10+
11+
Task<PushMessage> DequeueAsync(CancellationToken cancellationToken);
12+
}
13+
}

Demo.AspNetCore.PushNotifications.Services.Abstractions/IPushSubscriptionStore.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Lib.Net.Http.WebPush;
45

@@ -11,5 +12,7 @@ public interface IPushSubscriptionStore
1112
Task DiscardSubscriptionAsync(string endpoint);
1213

1314
Task ForEachSubscriptionAsync(Action<PushSubscription> action);
15+
16+
Task ForEachSubscriptionAsync(Action<PushSubscription> action, CancellationToken cancellationToken);
1417
}
1518
}

Demo.AspNetCore.PushNotifications.Services.PushService/PushServicePushNotificationService.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.Extensions.Options;
45
using Microsoft.Extensions.Logging;
@@ -33,11 +34,16 @@ public PushServicePushNotificationService(IOptions<PushNotificationServiceOption
3334
_logger = logger;
3435
}
3536

36-
public async Task SendNotificationAsync(PushSubscription subscription, PushMessage message)
37+
public Task SendNotificationAsync(PushSubscription subscription, PushMessage message)
38+
{
39+
return SendNotificationAsync(subscription, message, CancellationToken.None);
40+
}
41+
42+
public async Task SendNotificationAsync(PushSubscription subscription, PushMessage message, CancellationToken cancellationToken)
3743
{
3844
try
3945
{
40-
await _pushClient.RequestPushMessageDeliveryAsync(subscription, message);
46+
await _pushClient.RequestPushMessageDeliveryAsync(subscription, message, cancellationToken);
4147
}
4248
catch (Exception ex)
4349
{

Demo.AspNetCore.PushNotifications.Services.Sqlite/SqlitePushSubscriptionStore.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.EntityFrameworkCore;
45
using Lib.Net.Http.WebPush;
@@ -33,7 +34,12 @@ public async Task DiscardSubscriptionAsync(string endpoint)
3334

3435
public Task ForEachSubscriptionAsync(Action<PushSubscription> action)
3536
{
36-
return _context.Subscriptions.AsNoTracking().ForEachAsync(action);
37+
return ForEachSubscriptionAsync(action, CancellationToken.None);
38+
}
39+
40+
public Task ForEachSubscriptionAsync(Action<PushSubscription> action, CancellationToken cancellationToken)
41+
{
42+
return _context.Subscriptions.AsNoTracking().ForEachAsync(action, cancellationToken);
3743
}
3844
}
3945
}

Demo.AspNetCore.PushNotifications.Services/Demo.AspNetCore.PushNotifications.Services.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8+
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.1" />
89
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" />
910
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
1011
</ItemGroup>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Lib.Net.Http.WebPush;
7+
using Demo.AspNetCore.PushNotifications.Services.Abstractions;
8+
9+
namespace Demo.AspNetCore.PushNotifications.Services
10+
{
11+
internal class PushNotificationsDequeuer : IHostedService
12+
{
13+
private readonly IServiceProvider _serviceProvider;
14+
private readonly IPushNotificationsQueue _messagesQueue;
15+
private readonly IPushNotificationService _notificationService;
16+
private readonly CancellationTokenSource _stopTokenSource = new CancellationTokenSource();
17+
18+
private Task _dequeueMessagesTask;
19+
20+
public PushNotificationsDequeuer(IServiceProvider serviceProvider, IPushNotificationsQueue messagesQueue, IPushNotificationService notificationService)
21+
{
22+
_serviceProvider = serviceProvider;
23+
_messagesQueue = messagesQueue;
24+
_notificationService = notificationService;
25+
}
26+
27+
public Task StartAsync(CancellationToken cancellationToken)
28+
{
29+
_dequeueMessagesTask = Task.Run(DequeueMessagesAsync);
30+
31+
return Task.CompletedTask;
32+
}
33+
34+
public Task StopAsync(CancellationToken cancellationToken)
35+
{
36+
_stopTokenSource.Cancel();
37+
38+
return Task.WhenAny(_dequeueMessagesTask, Task.Delay(Timeout.Infinite, cancellationToken));
39+
}
40+
41+
private async Task DequeueMessagesAsync()
42+
{
43+
while (!_stopTokenSource.IsCancellationRequested)
44+
{
45+
PushMessage message = await _messagesQueue.DequeueAsync(_stopTokenSource.Token);
46+
47+
if (!_stopTokenSource.IsCancellationRequested)
48+
{
49+
using (IServiceScope serviceScope = _serviceProvider.CreateScope())
50+
{
51+
IPushSubscriptionStore subscriptionStore = serviceScope.ServiceProvider.GetRequiredService<IPushSubscriptionStore>();
52+
53+
await subscriptionStore.ForEachSubscriptionAsync((PushSubscription subscription) =>
54+
{
55+
// Fire-and-forget
56+
_notificationService.SendNotificationAsync(subscription, message, _stopTokenSource.Token);
57+
}, _stopTokenSource.Token);
58+
}
59+
60+
}
61+
}
62+
63+
}
64+
}
65+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using System.Collections.Concurrent;
5+
using Lib.Net.Http.WebPush;
6+
using Demo.AspNetCore.PushNotifications.Services.Abstractions;
7+
8+
namespace Demo.AspNetCore.PushNotifications.Services
9+
{
10+
internal class PushNotificationsQueue : IPushNotificationsQueue
11+
{
12+
private readonly ConcurrentQueue<PushMessage> _messages = new ConcurrentQueue<PushMessage>();
13+
private readonly SemaphoreSlim _messageEnqueuedSignal = new SemaphoreSlim(0);
14+
15+
public void Enqueue(PushMessage message)
16+
{
17+
if (message == null)
18+
{
19+
throw new ArgumentNullException(nameof(message));
20+
}
21+
22+
_messages.Enqueue(message);
23+
24+
_messageEnqueuedSignal.Release();
25+
}
26+
27+
public async Task<PushMessage> DequeueAsync(CancellationToken cancellationToken)
28+
{
29+
await _messageEnqueuedSignal.WaitAsync(cancellationToken);
30+
31+
_messages.TryDequeue(out PushMessage message);
32+
33+
return message;
34+
}
35+
}
36+
}

Demo.AspNetCore.PushNotifications.Services/ServiceCollectionExtensions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using Microsoft.Extensions.Hosting;
22
using Microsoft.Extensions.Configuration;
33
using Microsoft.Extensions.DependencyInjection;
44
using Demo.AspNetCore.PushNotifications.Services.Abstractions;
@@ -27,5 +27,13 @@ public static IServiceCollection AddPushNotificationService(this IServiceCollect
2727

2828
return services;
2929
}
30+
31+
public static IServiceCollection AddPushNotificationsQueue(this IServiceCollection services)
32+
{
33+
services.AddSingleton<IPushNotificationsQueue, PushNotificationsQueue>();
34+
services.AddSingleton<IHostedService, PushNotificationsDequeuer>();
35+
36+
return services;
37+
}
3038
}
3139
}

Demo.AspNetCore.PushNotifications/Controllers/PushNotificationsApiController.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ public class PushNotificationsApiController : Controller
1111
{
1212
private readonly IPushSubscriptionStore _subscriptionStore;
1313
private readonly IPushNotificationService _notificationService;
14+
private readonly IPushNotificationsQueue _pushNotificationsQueue;
1415

15-
public PushNotificationsApiController(IPushSubscriptionStore subscriptionStore, IPushNotificationService notificationService)
16+
public PushNotificationsApiController(IPushSubscriptionStore subscriptionStore, IPushNotificationService notificationService, IPushNotificationsQueue pushNotificationsQueue)
1617
{
1718
_subscriptionStore = subscriptionStore;
1819
_notificationService = notificationService;
20+
_pushNotificationsQueue = pushNotificationsQueue;
1921
}
2022

2123
// GET push-notifications-api/public-key
@@ -45,19 +47,12 @@ public async Task<IActionResult> DiscardSubscription(string endpoint)
4547

4648
// POST push-notifications-api/notifications
4749
[HttpPost("notifications")]
48-
public async Task<IActionResult> SendNotification([FromBody]PushMessageViewModel message)
50+
public IActionResult SendNotification([FromBody]PushMessageViewModel message)
4951
{
50-
PushMessage pushMessage = new PushMessage(message.Notification)
52+
_pushNotificationsQueue.Enqueue(new PushMessage(message.Notification)
5153
{
5254
Topic = message.Topic,
5355
Urgency = message.Urgency
54-
};
55-
56-
// TODO: This should be scheduled in background
57-
await _subscriptionStore.ForEachSubscriptionAsync((PushSubscription subscription) =>
58-
{
59-
// Fire-and-forget
60-
_notificationService.SendNotificationAsync(subscription, pushMessage);
6156
});
6257

6358
return NoContent();

0 commit comments

Comments
 (0)