Skip to content

Commit e8ba03a

Browse files
VCST-430: Simplify event handlers registration (#2769)
1 parent d754d45 commit e8ba03a

File tree

16 files changed

+199
-61
lines changed

16 files changed

+199
-61
lines changed

src/VirtoCommerce.Platform.Caching/PlatformMemoryCache.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public PlatformMemoryCache(IMemoryCache memoryCache, IOptions<CachingOptions> op
2121
SlidingExpiration = cachingOptions.CacheSlidingExpiration;
2222
_log = log;
2323
}
24-
24+
2525
public virtual ICacheEntry CreateEntry(object key)
2626
{
2727
var result = _memoryCache.CreateEntry(key);
@@ -88,7 +88,7 @@ public void Dispose()
8888
{
8989
Dispose(true);
9090
// This object will be cleaned up by the Dispose method.
91-
// Therefore, you should call GC.SupressFinalize to
91+
// Therefore, you should call GC.SuppressFinalize to
9292
// take this object off the finalization queue
9393
// and prevent finalization code for this object
9494
// from executing a second time.
@@ -107,10 +107,10 @@ protected virtual void Dispose(bool disposing)
107107
}
108108
}
109109

110-
110+
111111
protected virtual void EvictionCallback(object key, object value, EvictionReason reason, object state)
112112
{
113113
_log.LogTrace($"EvictionCallback: Cache entry with key:{key} has been removed.");
114-
}
114+
}
115115
}
116116
}

src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
namespace VirtoCommerce.Platform.Core.Bus
1010
{
11+
[DebuggerDisplay("{HandlerModuleName} {EventType.Name}")]
1112
public sealed class HandlerWrapper
1213
{
13-
public string EventName { get; set; }
14+
public Type EventType { get; set; }
1415

1516
public string HandlerModuleName { get; set; }
1617

@@ -26,8 +27,9 @@ public Task Handle(IEvent @event, CancellationToken cancellationToken)
2627
Handler(@event, cancellationToken).GetAwaiter().GetResult();
2728
stopWatch.Stop();
2829

29-
Logger.LogInformation($"event:{EventName} module:{HandlerModuleName} overall_elapsed:{stopWatch.ElapsedMilliseconds}");
30-
});
30+
Logger.LogInformation("event:{Event} module:{Handler} overall_elapsed:{Elapsed}",
31+
@event.GetType().Name, HandlerModuleName, stopWatch.ElapsedMilliseconds);
32+
}, cancellationToken);
3133
}
3234
}
3335
}

src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace VirtoCommerce.Platform.Core.Bus
77
{
88
public interface IHandlerRegistrar
99
{
10-
void RegisterHandler<T>(Func<T, CancellationToken,Task> handler) where T : class, IMessage;
10+
[Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")]
11+
void RegisterHandler<T>(Func<T, CancellationToken, Task> handler) where T : IMessage;
1112
}
1213
}

src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs

+47-13
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,74 @@
99

1010
namespace VirtoCommerce.Platform.Core.Bus
1111
{
12-
public class InProcessBus : IEventPublisher, IHandlerRegistrar
12+
public class InProcessBus : IEventHandlerRegistrar, IEventPublisher, IHandlerRegistrar
1313
{
1414
private readonly ILogger<InProcessBus> _logger;
15-
private readonly Dictionary<Type, List<HandlerWrapper>> _handlersByType = new Dictionary<Type, List<HandlerWrapper>>();
15+
private readonly List<HandlerWrapper> _handlers = [];
1616

1717
public InProcessBus(ILogger<InProcessBus> logger)
1818
{
1919
_logger = logger;
2020
}
2121

22-
public void RegisterHandler<T>(Func<T, CancellationToken, Task> handler) where T : class, IMessage
22+
public void RegisterEventHandler<T>(Func<T, Task> handler)
23+
where T : IEvent
2324
{
24-
if (!_handlersByType.TryGetValue(typeof(T), out var handlers))
25+
var eventType = typeof(T);
26+
27+
var handlerWrapper = new HandlerWrapper
2528
{
26-
handlers = new List<HandlerWrapper>();
27-
_handlersByType.Add(typeof(T), handlers);
28-
}
29+
EventType = eventType,
30+
HandlerModuleName = handler.Target?.GetType().Module.Assembly.GetName().Name,
31+
Handler = (message, _) => handler((T)message),
32+
Logger = _logger
33+
};
34+
35+
_handlers.Add(handlerWrapper);
36+
}
37+
38+
public void RegisterEventHandler<T>(Func<T, CancellationToken, Task> handler)
39+
where T : IEvent
40+
{
41+
#pragma warning disable VC0008 // Type or member is obsolete
42+
RegisterHandler(handler);
43+
#pragma warning restore VC0008 // Type or member is obsolete
44+
}
45+
46+
[Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")]
47+
public void RegisterHandler<T>(Func<T, CancellationToken, Task> handler)
48+
where T : IMessage
49+
{
50+
var eventType = typeof(T);
2951

3052
var handlerWrapper = new HandlerWrapper
3153
{
32-
EventName = typeof(T).Name,
33-
HandlerModuleName = handler.Target.GetType().Module.Assembly.GetName().Name,
54+
EventType = eventType,
55+
HandlerModuleName = handler.Target?.GetType().Module.Assembly.GetName().Name,
3456
Handler = (message, token) => handler((T)message, token),
3557
Logger = _logger
3658
};
3759

38-
handlers.Add(handlerWrapper);
60+
_handlers.Add(handlerWrapper);
3961
}
4062

41-
public async Task Publish<T>(T @event, CancellationToken cancellationToken = default(CancellationToken)) where T : class, IEvent
63+
public async Task Publish<T>(T @event, CancellationToken cancellationToken = default)
64+
where T : IEvent
4265
{
43-
if (!EventSuppressor.EventsSuppressed && _handlersByType.TryGetValue(@event.GetType(), out var handlers))
66+
if (EventSuppressor.EventsSuppressed)
67+
{
68+
return;
69+
}
70+
71+
var eventType = @event.GetType();
72+
73+
var handlers = _handlers
74+
.Where(x => x.EventType.IsAssignableFrom(eventType))
75+
.ToList();
76+
77+
if (handlers.Count > 0)
4478
{
45-
await Task.WhenAll(handlers.Select(handler => handler.Handle(@event, cancellationToken)));
79+
await Task.WhenAll(handlers.Select(x => x.Handle(@event, cancellationToken)));
4680
}
4781
}
4882
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace VirtoCommerce.Platform.Core.Events;
8+
9+
public static class ApplicationBuilderExtensions
10+
{
11+
public static IApplicationBuilder RegisterEventHandler<TEvent, THandler>(this IApplicationBuilder applicationBuilder)
12+
where TEvent : IEvent
13+
where THandler : IEventHandler<TEvent>
14+
{
15+
var handler = applicationBuilder.ApplicationServices.GetRequiredService<THandler>();
16+
return applicationBuilder.RegisterEventHandler<TEvent>(handler.Handle);
17+
}
18+
19+
public static IApplicationBuilder RegisterEventHandler<TEvent>(this IApplicationBuilder applicationBuilder, Func<TEvent, Task> handler)
20+
where TEvent : IEvent
21+
{
22+
var registrar = applicationBuilder.ApplicationServices.GetRequiredService<IEventHandlerRegistrar>();
23+
registrar.RegisterEventHandler(handler);
24+
return applicationBuilder;
25+
}
26+
27+
public static IApplicationBuilder RegisterCancellableEventHandler<TEvent, THandler>(this IApplicationBuilder applicationBuilder)
28+
where TEvent : IEvent
29+
where THandler : ICancellableEventHandler<TEvent>
30+
{
31+
var handler = applicationBuilder.ApplicationServices.GetRequiredService<THandler>();
32+
return applicationBuilder.RegisterCancellableEventHandler<TEvent>(handler.Handle);
33+
}
34+
35+
public static IApplicationBuilder RegisterCancellableEventHandler<TEvent>(this IApplicationBuilder applicationBuilder, Func<TEvent, CancellationToken, Task> handler)
36+
where TEvent : IEvent
37+
{
38+
var registrar = applicationBuilder.ApplicationServices.GetRequiredService<IEventHandlerRegistrar>();
39+
registrar.RegisterEventHandler(handler);
40+
return applicationBuilder;
41+
}
42+
}

src/VirtoCommerce.Platform.Core/Events/EventSuppressor.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,23 @@ private void Dispose(bool disposing)
3131
}
3232
}
3333

34-
private static readonly AsyncLocal<bool> EventsSuppressedStorage = new AsyncLocal<bool>();
34+
private static readonly AsyncLocal<bool> _eventsSuppressedStorage = new();
3535

3636
/// <summary>
3737
/// The flag indicates that events are suppressed for the current asynchronous control flow
3838
/// </summary>
39-
public static bool EventsSuppressed => EventsSuppressedStorage.Value;
39+
public static bool EventsSuppressed => _eventsSuppressedStorage.Value;
40+
41+
[Obsolete("Use SuppressEvents()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions/")]
42+
public static IDisposable SupressEvents() => SuppressEvents();
4043

4144
/// <summary>
4245
/// The flag indicates that events are suppressed for the current asynchronous control flow
4346
/// </summary>
44-
public static IDisposable SupressEvents()
47+
public static IDisposable SuppressEvents()
4548
{
46-
EventsSuppressedStorage.Value = true;
47-
return new DisposableActionGuard(() => { EventsSuppressedStorage.Value = false; });
49+
_eventsSuppressedStorage.Value = true;
50+
return new DisposableActionGuard(() => { _eventsSuppressedStorage.Value = false; });
4851
}
4952
}
5053
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace VirtoCommerce.Platform.Core.Events;
6+
7+
public interface IEventHandlerRegistrar
8+
{
9+
void RegisterEventHandler<T>(Func<T, Task> handler) where T : IEvent;
10+
void RegisterEventHandler<T>(Func<T, CancellationToken, Task> handler) where T : IEvent;
11+
}

src/VirtoCommerce.Platform.Core/Events/IEventPublisher.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ namespace VirtoCommerce.Platform.Core.Events
55
{
66
public interface IEventPublisher
77
{
8-
Task Publish<T>(T @event, CancellationToken cancellationToken = default(CancellationToken)) where T : class, IEvent;
8+
Task Publish<T>(T @event, CancellationToken cancellationToken = default) where T : IEvent;
99
}
1010
}

src/VirtoCommerce.Platform.Data/ExportImport/PlatformExportImportManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public async Task ImportAsync(Stream inputStream, PlatformExportManifest importO
129129
progressCallback(progressInfo);
130130

131131
using (var zipArchive = new ZipArchive(inputStream, ZipArchiveMode.Read, true))
132-
using (EventSuppressor.SupressEvents())
132+
using (EventSuppressor.SuppressEvents())
133133
{
134134
//Import selected platform entries
135135
await ImportPlatformEntriesInternalAsync(zipArchive, importOptions, progressCallback, сancellationToken);

src/VirtoCommerce.Platform.Data/Extensions/ServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static IServiceCollection AddPlatformServices(this IServiceCollection ser
3838
services.AddDynamicProperties();
3939

4040
services.AddSingleton<InProcessBus>();
41+
services.AddSingleton<IEventHandlerRegistrar>(x => x.GetRequiredService<InProcessBus>());
4142
services.AddSingleton<IHandlerRegistrar>(x => x.GetRequiredService<InProcessBus>());
4243
services.AddSingleton<IEventPublisher>(x => x.GetRequiredService<InProcessBus>());
4344
services.AddTransient<IChangeLogService, ChangeLogService>();

src/VirtoCommerce.Platform.Hangfire/Extensions/ApplicationBuilderExtensions.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.Options;
11-
using VirtoCommerce.Platform.Core.Bus;
11+
using VirtoCommerce.Platform.Core.Events;
1212
using VirtoCommerce.Platform.Core.Security;
1313
using VirtoCommerce.Platform.Core.Settings;
1414
using VirtoCommerce.Platform.Core.Settings.Events;
@@ -61,10 +61,9 @@ public static IApplicationBuilder UseHangfire(this IApplicationBuilder appBuilde
6161
var mvcJsonOptions = appBuilder.ApplicationServices.GetService<IOptions<MvcNewtonsoftJsonOptions>>();
6262
GlobalConfiguration.Configuration.UseSerializerSettings(mvcJsonOptions.Value.SerializerSettings);
6363

64-
var inProcessBus = appBuilder.ApplicationServices.GetService<IHandlerRegistrar>();
6564
var recurringJobManager = appBuilder.ApplicationServices.GetService<IRecurringJobManager>();
6665
var settingsManager = appBuilder.ApplicationServices.GetService<ISettingsManager>();
67-
inProcessBus.RegisterHandler<ObjectSettingChangedEvent>(async (message, token) => await recurringJobManager.HandleSettingChangeAsync(settingsManager, message));
66+
appBuilder.RegisterEventHandler<ObjectSettingChangedEvent>(message => recurringJobManager.HandleSettingChangeAsync(settingsManager, message));
6867

6968
// Add Hangfire filters/middlewares
7069
var userNameResolver = appBuilder.ApplicationServices.CreateScope().ServiceProvider.GetRequiredService<IUserNameResolver>();

src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs

+9-10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
using Microsoft.AspNetCore.Builder;
44
using Microsoft.Extensions.DependencyInjection;
55
using VirtoCommerce.Platform.Core;
6-
using VirtoCommerce.Platform.Core.Bus;
76
using VirtoCommerce.Platform.Core.Common;
7+
using VirtoCommerce.Platform.Core.Events;
88
using VirtoCommerce.Platform.Core.Security;
99
using VirtoCommerce.Platform.Core.Security.Events;
1010
using VirtoCommerce.Platform.Core.Settings;
@@ -29,15 +29,14 @@ public static IApplicationBuilder UsePlatformPermissions(this IApplicationBuilde
2929

3030
public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder appBuilder)
3131
{
32-
var inProcessBus = appBuilder.ApplicationServices.GetService<IHandlerRegistrar>();
33-
inProcessBus.RegisterHandler<UserChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
34-
inProcessBus.RegisterHandler<UserChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<UserApiKeyActualizeEventHandler>().Handle(message));
35-
inProcessBus.RegisterHandler<UserPasswordChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
36-
inProcessBus.RegisterHandler<UserResetPasswordEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
37-
inProcessBus.RegisterHandler<UserLoginEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
38-
inProcessBus.RegisterHandler<UserLogoutEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
39-
inProcessBus.RegisterHandler<UserRoleAddedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
40-
inProcessBus.RegisterHandler<UserRoleRemovedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
32+
appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
33+
appBuilder.RegisterEventHandler<UserChangedEvent, UserApiKeyActualizeEventHandler>();
34+
appBuilder.RegisterEventHandler<UserPasswordChangedEvent, LogChangesUserChangedEventHandler>();
35+
appBuilder.RegisterEventHandler<UserResetPasswordEvent, LogChangesUserChangedEventHandler>();
36+
appBuilder.RegisterEventHandler<UserLoginEvent, LogChangesUserChangedEventHandler>();
37+
appBuilder.RegisterEventHandler<UserLogoutEvent, LogChangesUserChangedEventHandler>();
38+
appBuilder.RegisterEventHandler<UserRoleAddedEvent, LogChangesUserChangedEventHandler>();
39+
appBuilder.RegisterEventHandler<UserRoleRemovedEvent, LogChangesUserChangedEventHandler>();
4140

4241
return appBuilder;
4342
}

src/VirtoCommerce.Platform.Web/Security/ServiceCollectionExtensions.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.AspNetCore.Identity;
55
using Microsoft.Extensions.DependencyInjection;
66
using Microsoft.Extensions.DependencyInjection.Extensions;
7-
using VirtoCommerce.Platform.Core.ChangeLog;
87
using VirtoCommerce.Platform.Core.Common;
98
using VirtoCommerce.Platform.Core.Security;
109
using VirtoCommerce.Platform.Core.Security.Search;
@@ -23,8 +22,8 @@ public static IServiceCollection AddSecurityServices(this IServiceCollection ser
2322
services.AddTransient<ISecurityRepository, SecurityRepository>();
2423
services.AddTransient<Func<ISecurityRepository>>(provider => () => provider.CreateScope().ServiceProvider.GetService<ISecurityRepository>());
2524

26-
services.AddScoped<IUserApiKeyService, UserApiKeyService>();
27-
services.AddScoped<IUserApiKeySearchService, UserApiKeySearchService>();
25+
services.AddSingleton<IUserApiKeyService, UserApiKeyService>();
26+
services.AddSingleton<IUserApiKeySearchService, UserApiKeySearchService>();
2827

2928
services.AddScoped<IUserNameResolver, HttpContextUserResolver>();
3029
services.AddSingleton<IPermissionsRegistrar, DefaultPermissionProvider>();
@@ -57,8 +56,8 @@ public static IServiceCollection AddSecurityServices(this IServiceCollection ser
5756
services.Configure(setupAction);
5857
}
5958

60-
services.AddSingleton(provider => new LogChangesUserChangedEventHandler(provider.CreateScope().ServiceProvider.GetService<IChangeLogService>()));
61-
services.AddSingleton(provider => new UserApiKeyActualizeEventHandler(provider.CreateScope().ServiceProvider.GetService<IUserApiKeyService>()));
59+
services.AddSingleton<LogChangesUserChangedEventHandler>();
60+
services.AddSingleton<UserApiKeyActualizeEventHandler>();
6261

6362
services.AddTransient<IServerCertificateService, ServerCertificateService>();
6463

tests/VirtoCommerce.Platform.Tests/Common/CountriesServiceTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading.Tasks;
23
using Moq;
34
using Nager.Country;
45
using VirtoCommerce.Platform.Core.Common;
@@ -10,12 +11,12 @@ namespace VirtoCommerce.Platform.Tests.Common
1011
public class CountriesServiceTests
1112
{
1213
[Fact]
13-
public void CanGetCountries()
14+
public async Task CanGetCountries()
1415
{
1516
var filesystemCountryService = new Mock<ICountriesService>();
1617
var service = new CountriesService(filesystemCountryService.Object as FileSystemCountriesService);
1718

18-
var countries = service.GetCountriesAsync().GetAwaiter().GetResult();
19+
var countries = await service.GetCountriesAsync();
1920

2021
Assert.Equal(249, countries.Count);
2122
}

0 commit comments

Comments
 (0)