Added modular event system

This commit is contained in:
2025-02-01 11:50:52 +01:00
parent 966ced57d6
commit 39641f18a8
22 changed files with 262 additions and 68 deletions

View File

@@ -1,13 +1,16 @@
using Microsoft.EntityFrameworkCore;
using HopFrame.Core.Events;
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Config;
public class DbContextConfig {
public Type ContextType { get; }
public List<TableConfig> Tables { get; } = new();
public HopFrameConfig ParentConfig { get; }
public DbContextConfig(Type context) {
public DbContextConfig(Type context, HopFrameConfig parentConfig) {
ContextType = context;
ParentConfig = parentConfig;
foreach (var property in ContextType.GetProperties()) {
if (!property.PropertyType.IsGenericType) continue;

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using HopFrame.Core.Events;
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Config;
@@ -7,6 +8,7 @@ public class HopFrameConfig {
public bool DisplayUserInfo { get; set; } = true;
public string? BasePolicy { get; set; }
public string? LoginPageRewrite { get; set; }
public List<HopEventHandler> Handlers { get; } = new();
}
/// <summary>
@@ -38,7 +40,7 @@ public class HopFrameConfigurator(HopFrameConfig config) {
/// <returns>The configurator used for the DbContext</returns>
/// <seealso cref="DbContextConfigurator{TDbContext}"/>
public DbContextConfigurator<TDbContext> AddDbContext<TDbContext>() where TDbContext : DbContext {
var context = new DbContextConfig(typeof(TDbContext));
var context = new DbContextConfig(typeof(TDbContext), InnerConfig);
InnerConfig.Contexts.Add(context);
return new DbContextConfigurator<TDbContext>(context);
}

View File

@@ -2,6 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq.Expressions;
using System.Reflection;
using HopFrame.Core.Events;
namespace HopFrame.Core.Config;
@@ -188,6 +189,36 @@ public class TableConfigurator<TModel>(TableConfig config) {
InnerConfig.DeletePolicy = policy;
return this;
}
/// <summary>
/// Adds an event handler of the provided type
/// </summary>
/// <param name="type">The type of event that triggers the handler</param>
/// <param name="handler">The handler delegate</param>
public TableConfigurator<TModel> AddEventHandler(EventType type, Func<TModel, IServiceProvider, Task> handler) {
var eventName = EventTypes.ConstructEventName(type, InnerConfig);
var handlerStore = new HopEventHandler(eventName, (o, provider) => handler.Invoke((TModel)o, provider));
InnerConfig.ContextConfig.ParentConfig.Handlers.Add(handlerStore);
return this;
}
/// <summary>
/// Adds an event handler of the provided type
/// </summary>
/// <param name="type">The type of event that triggers the handler</param>
/// <param name="handler">The handler delegate</param>
public TableConfigurator<TModel> AddEventHandler(EventType type, Action<TModel, IServiceProvider> handler) {
var eventName = EventTypes.ConstructEventName(type, InnerConfig);
var handlerStore = new HopEventHandler(eventName, (o, provider) => {
handler.Invoke((TModel)o, provider);
return Task.CompletedTask;
});
InnerConfig.ContextConfig.ParentConfig.Handlers.Add(handlerStore);
return this;
}
internal static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda) {
if (propertyLambda.Body is not MemberExpression member) {

View File

@@ -0,0 +1,31 @@
using HopFrame.Core.Config;
namespace HopFrame.Core.Events;
public static class EventTypes {
private const string Prefix = "HopFrame.";
private const string CreateEntryPrefix = Prefix + "Entry.Create.";
private const string UpdateEntryPrefix = Prefix + "Entry.Update.";
private const string DeleteEntryPrefix = Prefix + "Entry.Delete.";
public static string CreateEntry(TableConfig config) => CreateEntryPrefix + config.PropertyName;
public static string UpdateEntry(TableConfig config) => UpdateEntryPrefix + config.PropertyName;
public static string DeleteEntry(TableConfig config) => DeleteEntryPrefix + config.PropertyName;
public static string ConstructEventName(EventType type, TableConfig config) {
return type switch {
EventType.CreateEntry => CreateEntry(config),
EventType.UpdateEntry => UpdateEntry(config),
EventType.DeleteEntry => DeleteEntry(config),
_ => Prefix
};
}
}
public enum EventType {
CreateEntry = 0,
UpdateEntry = 1,
DeleteEntry = 2
}

View File

@@ -0,0 +1,7 @@
namespace HopFrame.Core.Events;
public readonly struct HopEventHandler(string eventType, Func<object, IServiceProvider, Task> handler) {
public Guid Id { get; } = Guid.CreateVersion7();
public Func<object, IServiceProvider, Task> Handler { get; } = handler;
public string EventType { get; } = eventType;
}

View File

@@ -0,0 +1,14 @@
namespace HopFrame.Core.Events;
public interface IEventEmitter {
Guid RegisterEventHandler(string @event, Func<object, IServiceProvider, Task> handler);
bool RemoveEventHandler(Guid id);
Task DispatchEvent(string @event, object argument = null!);
void RemoveAllEventHandlers(string @event);
void RemoveAllEventHandlers();
}

View File

@@ -1,4 +1,5 @@
using HopFrame.Core.Services;
using HopFrame.Core.Events;
using HopFrame.Core.Services;
using HopFrame.Core.Services.Implementations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -15,6 +16,7 @@ public static class ServiceCollectionExtensions {
public static IServiceCollection AddHopFrameServices(this IServiceCollection services) {
services.AddScoped<IContextExplorer, ContextExplorer>();
services.TryAddScoped<IHopFrameAuthHandler, DefaultAuthHandler>();
services.TryAddSingleton<IEventEmitter, EventEmitter>();
return services;
}

View File

@@ -0,0 +1,39 @@
using HopFrame.Core.Config;
using HopFrame.Core.Events;
namespace HopFrame.Core.Services.Implementations;
internal sealed class EventEmitter(IServiceProvider provider, HopFrameConfig config) : IEventEmitter {
public Guid RegisterEventHandler(string @event, Func<object, IServiceProvider, Task> handler) {
var handlerStore = new HopEventHandler(@event, handler);
config.Handlers.Add(handlerStore);
return handlerStore.Id;
}
public bool RemoveEventHandler(Guid id) {
var count = config.Handlers.RemoveAll(handler => handler.Id == id);
return count > 0;
}
public async Task DispatchEvent(string @event, object argument = null!) {
var handlers = config.Handlers.Where(handler => handler.EventType == @event);
var tasks = new List<Task>();
foreach (var handler in handlers) {
var task = handler.Handler.Invoke(argument, provider);
tasks.Add(task);
}
await Task.WhenAll(tasks);
}
public void RemoveAllEventHandlers(string @event) {
config.Handlers.RemoveAll(handler => handler.EventType == @event);
}
public void RemoveAllEventHandlers() {
config.Handlers.Clear();
}
}