Init
This commit is contained in:
177
Prefab/Module/Extensions.cs
Normal file
177
Prefab/Module/Extensions.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Prefab.Data;
|
||||
using Prefab.Data.Seeder;
|
||||
using Prefab.Endpoints;
|
||||
|
||||
namespace Prefab.Module;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering and configuring Prefab modules and endpoint registrars within an ASP.NET
|
||||
/// Core application.
|
||||
/// </summary>
|
||||
/// <remarks>The Extensions class contains methods intended to be called from the application's Program.cs file to
|
||||
/// integrate Prefab modules into the application's dependency injection and request pipeline. These methods enable
|
||||
/// modular composition by discovering and registering implementations of IModule and IEndpointRegistrar from
|
||||
/// application dependencies. This approach allows modules to participate in both the application's build and
|
||||
/// configuration phases, supporting extensibility and separation of concerns.</remarks>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Prefab modules, endpoint registrars, and related services to the specified WebApplicationBuilder for
|
||||
/// modular application composition.
|
||||
/// </summary>
|
||||
/// <remarks>This method scans application dependencies for types implementing IModule and
|
||||
/// IEndpointRegistrar, registers them as singletons, and invokes their Build methods to allow modules to
|
||||
/// participate in application setup. Call this method early in the application's startup configuration to ensure
|
||||
/// all modules are properly registered and initialized.</remarks>
|
||||
/// <param name="builder">The WebApplicationBuilder to which Prefab modules and services will be added. Cannot be null.</param>
|
||||
/// <returns>The same WebApplicationBuilder instance, configured with Prefab modules and services.</returns>
|
||||
public static WebApplicationBuilder AddPrefab(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddPrefab(builder.Configuration);
|
||||
|
||||
// Discover endpoint registrars through Scrutor so modules can contribute HTTP endpoints.
|
||||
builder.Services.Scan(scan => scan
|
||||
.FromApplicationDependencies(assembly => assembly.GetName().Name?.StartsWith(nameof(Prefab)) == true)
|
||||
.AddClasses(classes => classes.AssignableTo(typeof(IEndpointRegistrar)))
|
||||
.As<IEndpointRegistrar>()
|
||||
.WithSingletonLifetime());
|
||||
|
||||
// Discover modules via Scrutor and register them as singletons so they can participate in Build/Configure.
|
||||
builder.Services.Scan(scan => scan
|
||||
.FromApplicationDependencies(assembly => assembly.GetName().Name?.StartsWith(nameof(Prefab)) == true)
|
||||
.AddClasses(classes => classes.AssignableTo<IModule>())
|
||||
.As<IModule>()
|
||||
.WithSingletonLifetime());
|
||||
|
||||
var modules = new List<IModule>();
|
||||
|
||||
// Build a temporary provider so DI can instantiate modules before the real host is built.
|
||||
// Disposes it immediately but keeps the instances so Build/Configure run on the same objects.
|
||||
using (var provider = builder.Services.BuildServiceProvider(new ServiceProviderOptions
|
||||
{
|
||||
ValidateOnBuild = true,
|
||||
ValidateScopes = true
|
||||
}))
|
||||
{
|
||||
modules.AddRange(provider.GetServices<IModule>());
|
||||
}
|
||||
|
||||
foreach (var module in modules)
|
||||
{
|
||||
module.Build(builder);
|
||||
}
|
||||
|
||||
builder.Services.RemoveAll<IModule>();
|
||||
|
||||
foreach (var module in modules)
|
||||
{
|
||||
builder.Services.AddSingleton(module);
|
||||
}
|
||||
|
||||
builder.Services.AddPrefabHandlers();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application by invoking all registered modules and mapping endpoints using registered endpoint
|
||||
/// registrars.
|
||||
/// </summary>
|
||||
/// <remarks>This method discovers and executes all services implementing <see cref="IModule"/> and <see
|
||||
/// cref="IEndpointRegistrar"/> from the application's dependency injection container. Each module's configuration
|
||||
/// and endpoint mapping logic will be applied in the order they are registered.</remarks>
|
||||
/// <param name="app">The <see cref="WebApplication"/> instance to configure. Cannot be null.</param>
|
||||
/// <returns>The same <see cref="WebApplication"/> instance, enabling method chaining.</returns>
|
||||
public static WebApplication UsePrefab(this WebApplication app)
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
var seederTypes = scope.ServiceProvider.GetServices<ISeeder>()
|
||||
.Select(seeder => seeder.GetType())
|
||||
.ToList();
|
||||
|
||||
foreach (var type in seederTypes)
|
||||
{
|
||||
var mutexName = $"Global\\PrefabSeeder_{type.FullName}";
|
||||
var mutex = new Mutex(initiallyOwned: false, mutexName, out _);
|
||||
var acquired = false;
|
||||
|
||||
try
|
||||
{
|
||||
acquired = mutex.WaitOne(0);
|
||||
}
|
||||
catch (AbandonedMutexException)
|
||||
{
|
||||
acquired = true;
|
||||
}
|
||||
|
||||
if (!acquired)
|
||||
{
|
||||
mutex.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
Prefab.Data.Seeder.Extensions.Channel.Writer.TryWrite(async (_, cancellationToken) =>
|
||||
{
|
||||
using var workerScope = app.Services.CreateScope();
|
||||
var seeder = (ISeeder)workerScope.ServiceProvider.GetRequiredService(type);
|
||||
try
|
||||
{
|
||||
await seeder.Execute(workerScope.ServiceProvider, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mutex.ReleaseMutex();
|
||||
mutex.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Allow each module to run its Configure step
|
||||
foreach (var module in app.Services.GetServices<IModule>())
|
||||
{
|
||||
module.Configure(app);
|
||||
}
|
||||
|
||||
// Allow registered endpoint registrars to map HTTP endpoints.
|
||||
foreach (var registrar in app.Services.GetServices<IEndpointRegistrar>())
|
||||
{
|
||||
registrar.MapEndpoints(app);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers all interfaces implemented by the specified DbContext type that derive from IPrefabDb as scoped
|
||||
/// services in the dependency injection container.
|
||||
/// </summary>
|
||||
/// <remarks>Each interface derived from IPrefabDb that is implemented by the specified DbContext type is
|
||||
/// registered as a scoped service, resolving to the same DbContext instance. This ensures that all such interfaces
|
||||
/// share the same context and change tracking within a request scope.</remarks>
|
||||
/// <param name="services">The IServiceCollection to add the service registrations to.</param>
|
||||
/// <param name="dbContextType">The type of the DbContext whose IPrefabDb-derived interfaces will be registered. Must implement one or more
|
||||
/// interfaces that derive from IPrefabDb.</param>
|
||||
/// <returns>The IServiceCollection instance with the added service registrations. This enables method chaining.</returns>
|
||||
public static IServiceCollection AddModuleDbInterfaces(this IServiceCollection services, Type dbContextType)
|
||||
{
|
||||
var dbContextInterfaces = dbContextType.GetInterfaces()
|
||||
.Where(i => typeof(IPrefabDb).IsAssignableFrom(i))
|
||||
//.Where(i => typeof(IPrefabDb).IsAssignableFrom(i) || i == typeof(ISagaDb) || i == typeof(IQueueDb))
|
||||
.ToList();
|
||||
|
||||
foreach (var dbContextInterface in dbContextInterfaces)
|
||||
{
|
||||
// resolve module interfaces to the same DbContext instance that was registered
|
||||
// via AddDbContext so that all changes are tracked by a single context
|
||||
services.TryAdd(new ServiceDescriptor(
|
||||
dbContextInterface,
|
||||
sp => sp.GetRequiredService(dbContextType),
|
||||
ServiceLifetime.Scoped));
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user