I came across a situation where I had some dependencies which needed to run some code at launch. In this instance I wanted to register an event type at the application level for use in validation further down the line.
So achieve this is deceptively simple; we make use of a hosted service and scan our assembly for instances with a certain attribute.
The attribute, which we'll call Preload, doesn't contain any code and is just used to tag a class:
[AttributeUsage(AttributeTargets.Class)]
public class PreloadAttribute : Attribute
{
}
Then we use reflection on the start method of our hosted service to find the tagged types and then use the DI framework to get an instance of them:
public class InstantiationService(IServiceProvider serviceProvider) : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
using var scope = serviceProvider.CreateScope();
var taggedTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(assembly =>
{
try
{
return assembly.GetTypes();
}
catch
{
return [];
}
})
.Where(type =>
type.IsClass &&
!type.IsAbstract &&
type.GetCustomAttribute<PreloadAttribute>() != null
);
foreach (var type in taggedTypes)
{
scope.ServiceProvider.GetService(type);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
And in our Program.cs file we set it up with builder.Services.AddHostedService<InstantiationService>(); with the rest of our services.
A use case example of this is we have a class which wants to raise events, but we want these events to be strongly typed and scoped then used to validate against requests to subscribe to said events.
[Preload]
public class Thing : IThing
{
public enum ThingEvents
{
Create,
Update,
Delete
}
private readonly IEvents<ThingEvents> _events;
public Thing(IEvents<ThingEvents> events)
{
_events = events;
}
public void DoThing()
{
}
}
public class Events<T> : IEvents<T> where T : Enum
{
private readonly IEventRegistry _eventRegistry;
public Events(IEventRegistry eventRegistry)
{
_eventRegistry = eventRegistry;
RegisterEventType();
}
private void RegisterEventType()
{
var enumType = typeof(T);
var enumNames = Enum.GetNames(enumType);
foreach (var name in enumNames)
{
_eventRegistry.RegisterEventType(enumType.Name + "." + name);
}
}
public void Raise(T eventType, object payload)
{
}
}
public class EventRegistry : IEventRegistry
{
private readonly List<string> _eventTypes = [];
public void RegisterEventType(string eventName)
{
_eventTypes.Add(eventName);
Console.WriteLine("Adding "+eventName);
}
public bool IsValidEventType(string eventName) => _eventTypes.Contains(eventName);
}
var valid = IsValidEvent("ThingEvents.Create");
