Skip to content

Commit 259a682

Browse files
committed
Run Events handlers in the background
1 parent 6b843a6 commit 259a682

8 files changed

Lines changed: 85 additions & 50 deletions

File tree

FilesAPI/Controllers/StorageController.cs

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
using FilesAPI.ViewModels;
33
using FilesAPI.ViewModels.Mapper;
44
using Microsoft.AspNetCore.Mvc;
5-
using Microsoft.Extensions.DependencyInjection;
65
using Models;
6+
using Services;
77
using System;
88
using System.Collections.Generic;
99
using System.Linq;
@@ -17,10 +17,12 @@ namespace FilesAPI.Controllers;
1717
public class StorageController : ControllerBase
1818
{
1919
private readonly IStorageService _storageService;
20+
private readonly EventHandlerContainer _eventContainer;
2021

21-
public StorageController(IStorageService storageService)
22+
public StorageController(IStorageService storageService, EventHandlerContainer eventContainer)
2223
{
2324
_storageService = storageService;
25+
_eventContainer = eventContainer;
2426
}
2527

2628
//Example from https://dottutorials.net/dotnet-core-web-api-multipart-form-data-upload-file/
@@ -56,27 +58,8 @@ public async Task<IActionResult> DownLoadFile(string id, CancellationToken token
5658
{
5759
var (content, details) = await _storageService.DownloadFileAsync(id, token);
5860

59-
// Record analytics
60-
var userAgent = Request.Headers["User-Agent"].ToString();
61-
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
62-
var referrer = Request.Headers["Referer"].ToString();
63-
64-
// Fire and forget analytics recording
65-
_ = Task.Run(async () =>
66-
{
67-
try
68-
{
69-
var analyticsService = HttpContext.RequestServices.GetService<IAnalyticsService>();
70-
if (analyticsService != null)
71-
{
72-
await analyticsService.RecordDownloadAsync(id, userAgent, ipAddress, referrer, "download", token);
73-
}
74-
}
75-
catch
76-
{
77-
// Ignore analytics errors to not affect file download
78-
}
79-
}, token);
61+
// Record analytics for view
62+
await RecordAnalyticsAsync(details, token);
8063

8164
this.Response.ContentLength = details.Size;
8265
this.Response.Headers["Accept-Ranges"] = "bytes";
@@ -90,26 +73,7 @@ public async Task<FileStreamResult> DownloadView(string id, CancellationToken to
9073
var (stream, details) = await _storageService.DownloadFileAsync(id, token);
9174

9275
// Record analytics for view
93-
var userAgent = Request.Headers["User-Agent"].ToString();
94-
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
95-
var referrer = Request.Headers["Referer"].ToString();
96-
97-
// Fire and forget analytics recording
98-
_ = Task.Run(async () =>
99-
{
100-
try
101-
{
102-
var analyticsService = HttpContext.RequestServices.GetService<IAnalyticsService>();
103-
if (analyticsService != null)
104-
{
105-
await analyticsService.RecordDownloadAsync(id, userAgent, ipAddress, referrer, "view", token);
106-
}
107-
}
108-
catch
109-
{
110-
// Ignore analytics errors to not affect file download
111-
}
112-
}, token);
76+
await RecordAnalyticsAsync(details, token);
11377

11478
this.Response.ContentLength = details.Size;
11579
this.Response.Headers["Accept-Ranges"] = "bytes";
@@ -152,4 +116,22 @@ public async Task<ActionResult<string>> DeleteFileAsync(string id, CancellationT
152116
string deletedId = await _storageService.DeleteFileAsync(id, token);
153117
return Ok($"Deleted '{deletedId}' successfully.");
154118
}
119+
120+
private async Task RecordAnalyticsAsync(FileDetails details, CancellationToken token)
121+
{
122+
var userAgent = Request.Headers["User-Agent"].ToString();
123+
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
124+
var referrer = Request.Headers["Referer"].ToString();
125+
var enhancedFileDownloadedEvent = new EnhancedFileDownloadedEvent
126+
{
127+
DownloadMethod = "view",
128+
DownloadStartTime = DateTime.UtcNow,
129+
FileDetails = details,
130+
UserAgent = userAgent,
131+
IpAddress = ipAddress,
132+
Referrer = referrer,
133+
RequestId = HttpContext.TraceIdentifier
134+
};
135+
await _eventContainer.PublishAsync(enhancedFileDownloadedEvent, token);
136+
}
155137
}

FilesAPI/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ public void ConfigureServices(IServiceCollection services)
119119
services.AddSingleton<ISettingsService, SettingsService>();
120120

121121
services.AddScoped<RecordDownloadHandler>();
122+
services.AddScoped<RecordDownloadAnalyticsHandler>();
122123
services.AddScoped<EventHandlerContainer>();
123124
EventHandlerContainer.Subscribe<FileDownloadedEvent, RecordDownloadHandler>();
125+
EventHandlerContainer.Subscribe<EnhancedFileDownloadedEvent, RecordDownloadAnalyticsHandler>();
124126

125127
services.AddControllers();
126128
}

FilesAPI/database.db

0 Bytes
Binary file not shown.

Models/Events/EnhancedFileDownloadedEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ namespace Models;
33
/// <summary>
44
/// Enhanced file download event with additional analytics data
55
/// </summary>
6-
public class EnhancedFileDownloadedEvent
6+
public class EnhancedFileDownloadedEvent : EventBase
77
{
88
public FileDetails FileDetails { get; set; }
99
public DateTime DownloadStartTime { get; set; }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Services;
4+
5+
public class RecordDownloadAnalyticsHandler : IEventHandler<EnhancedFileDownloadedEvent>
6+
{
7+
private readonly IServiceScopeFactory _scopeFactory;
8+
9+
public RecordDownloadAnalyticsHandler(IServiceScopeFactory scopeFactory)
10+
{
11+
_scopeFactory = scopeFactory;
12+
}
13+
14+
public Task RunAsync(EnhancedFileDownloadedEvent obj, CancellationToken token)
15+
{
16+
_ = Task.Run(async () =>
17+
{
18+
//fire‑and‑forget
19+
using var scope = _scopeFactory.CreateScope();
20+
var analyticsService = scope.ServiceProvider.GetRequiredService<IAnalyticsService>();
21+
await analyticsService.RecordDownloadAsync(
22+
obj.FileDetails.Id,
23+
obj.UserAgent,
24+
obj.IpAddress,
25+
obj.Referrer,
26+
obj.DownloadMethod,
27+
CancellationToken.None);
28+
}, CancellationToken.None);
29+
30+
return Task.CompletedTask;
31+
}
32+
}
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
namespace Services;
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Services;
24

35
public class RecordDownloadHandler : IEventHandler<FileDownloadedEvent>
46
{
5-
private readonly IStorageService _storageService;
7+
private readonly IServiceScopeFactory _scopeFactory;
68

7-
public RecordDownloadHandler(IStorageService storageService)
9+
public RecordDownloadHandler(IServiceScopeFactory scopeFactory)
810
{
9-
_storageService = storageService;
11+
_scopeFactory = scopeFactory;
1012
}
1113

12-
public async Task RunAsync(FileDownloadedEvent obj, CancellationToken token)
14+
public Task RunAsync(FileDownloadedEvent obj, CancellationToken token)
1315
{
14-
await _storageService.IncrementDownloadCountAsync(obj.FileDetails, token);
16+
//fire‑and‑forget
17+
_ = Task.Run(async () =>
18+
{
19+
using var scope = _scopeFactory.CreateScope();
20+
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
21+
await storageService.IncrementDownloadCountAsync(obj.FileDetails, CancellationToken.None);
22+
}, CancellationToken.None);
23+
24+
return Task.CompletedTask;
1525
}
1626
}

Services/Repositories/MongoDbDownloadAnalyticsRepository.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ private void CreateIndexes(CancellationToken token = default)
3333

3434
public async Task<DownloadAnalytics> AddDownloadRecordAsync(DownloadAnalytics analytics, CancellationToken token)
3535
{
36+
if (string.IsNullOrEmpty(analytics.Id))
37+
{
38+
analytics.Id = ObjectId.NewObjectId().ToString();
39+
}
3640
await _collection.InsertOneAsync(analytics, options: default, token);
3741
return analytics;
3842
}

Services/Repositories/SqlDbDownloadAnalyticsRepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ public SqlDbDownloadAnalyticsRepository(FilesDbContext filesDbContext)
1414

1515
public async Task<DownloadAnalytics> AddDownloadRecordAsync(DownloadAnalytics analytics, CancellationToken token)
1616
{
17+
if (string.IsNullOrEmpty(analytics.Id))
18+
{
19+
analytics.Id = ObjectId.NewObjectId().ToString();
20+
}
1721
await _filesDbContext.AddAsync(analytics, token);
1822
await _filesDbContext.SaveChangesAsync(token);
23+
1924
return analytics;
2025
}
2126

0 commit comments

Comments
 (0)