Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,93 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.DependencyInjection;
using StellaOps.Plugin.Hosting;
using StellaOps.Plugin.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Plugin.DependencyInjection;
public static class PluginDependencyInjectionExtensions
{
public static IServiceCollection RegisterPluginRoutines(
this IServiceCollection services,
IConfiguration configuration,
PluginHostOptions options,
ILogger? logger = null)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
var loadResult = PluginHost.LoadPlugins(options, logger);
foreach (var plugin in loadResult.Plugins)
{
PluginServiceRegistration.RegisterAssemblyMetadata(services, plugin.Assembly, logger);
foreach (var routine in CreateRoutines(plugin.Assembly))
{
logger?.LogDebug(
"Registering DI routine '{RoutineType}' from plugin '{PluginAssembly}'.",
routine.GetType().FullName,
plugin.Assembly.FullName);
routine.Register(services, configuration);
}
}
if (loadResult.MissingOrderedPlugins.Count > 0)
{
logger?.LogWarning(
"Some ordered plugins were not found: {Missing}",
string.Join(", ", loadResult.MissingOrderedPlugins));
}
return services;
}
private static IEnumerable<IDependencyInjectionRoutine> CreateRoutines(System.Reflection.Assembly assembly)
{
foreach (var type in assembly.GetLoadableTypes())
{
if (type is null || type.IsAbstract || type.IsInterface)
{
continue;
}
if (!typeof(IDependencyInjectionRoutine).IsAssignableFrom(type))
{
continue;
}
object? instance;
try
{
instance = Activator.CreateInstance(type);
}
catch
{
continue;
}
if (instance is IDependencyInjectionRoutine routine)
{
yield return routine;
}
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.DependencyInjection;
using StellaOps.Plugin.Internal;
namespace StellaOps.Plugin.DependencyInjection;
public static class PluginServiceRegistration
{
public static void RegisterAssemblyMetadata(IServiceCollection services, Assembly assembly, ILogger? logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(assembly);
foreach (var implementationType in assembly.GetLoadableTypes())
{
if (implementationType is null || !implementationType.IsClass || implementationType.IsAbstract)
{
continue;
}
var attributes = implementationType.GetCustomAttributes<ServiceBindingAttribute>(inherit: false);
if (!attributes.Any())
{
continue;
}
foreach (var attribute in attributes)
{
try
{
ApplyBinding(services, implementationType, attribute, logger);
}
catch (Exception ex)
{
logger?.LogWarning(
ex,
"Failed to register service binding for implementation '{Implementation}' declared in assembly '{Assembly}'.",
implementationType.FullName ?? implementationType.Name,
assembly.FullName ?? assembly.GetName().Name);
}
}
}
}
private static void ApplyBinding(
IServiceCollection services,
Type implementationType,
ServiceBindingAttribute attribute,
ILogger? logger)
{
var serviceType = attribute.ServiceType ?? implementationType;
if (!IsValidBinding(serviceType, implementationType))
{
logger?.LogWarning(
"Service binding metadata ignored: implementation '{Implementation}' is not assignable to service '{Service}'.",
implementationType.FullName ?? implementationType.Name,
serviceType.FullName ?? serviceType.Name);
return;
}
if (attribute.ReplaceExisting)
{
RemoveExistingDescriptors(services, serviceType);
}
AddDescriptorIfMissing(services, serviceType, implementationType, attribute.Lifetime, logger);
if (attribute.RegisterAsSelf && serviceType != implementationType)
{
AddDescriptorIfMissing(services, implementationType, implementationType, attribute.Lifetime, logger);
}
}
private static bool IsValidBinding(Type serviceType, Type implementationType)
{
if (serviceType.IsGenericTypeDefinition)
{
return implementationType.IsGenericTypeDefinition
&& implementationType.IsClass
&& implementationType.IsAssignableToGenericTypeDefinition(serviceType);
}
return serviceType.IsAssignableFrom(implementationType);
}
private static void AddDescriptorIfMissing(
IServiceCollection services,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime,
ILogger? logger)
{
if (services.Any(descriptor =>
descriptor.ServiceType == serviceType &&
descriptor.ImplementationType == implementationType))
{
logger?.LogDebug(
"Skipping duplicate service binding for {ServiceType} -> {ImplementationType}.",
serviceType.FullName ?? serviceType.Name,
implementationType.FullName ?? implementationType.Name);
return;
}
ServiceDescriptor descriptor;
if (serviceType.IsGenericTypeDefinition || implementationType.IsGenericTypeDefinition)
{
descriptor = ServiceDescriptor.Describe(serviceType, implementationType, lifetime);
}
else
{
descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
}
services.Add(descriptor);
logger?.LogDebug(
"Registered service binding {ServiceType} -> {ImplementationType} with {Lifetime} lifetime.",
serviceType.FullName ?? serviceType.Name,
implementationType.FullName ?? implementationType.Name,
lifetime);
}
private static void RemoveExistingDescriptors(IServiceCollection services, Type serviceType)
{
for (var i = services.Count - 1; i >= 0; i--)
{
if (services[i].ServiceType == serviceType)
{
services.RemoveAt(i);
}
}
}
private static bool IsAssignableToGenericTypeDefinition(this Type implementationType, Type serviceTypeDefinition)
{
if (!serviceTypeDefinition.IsGenericTypeDefinition)
{
return false;
}
if (implementationType == serviceTypeDefinition)
{
return true;
}
if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == serviceTypeDefinition)
{
return true;
}
var interfaces = implementationType.GetInterfaces();
foreach (var iface in interfaces)
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == serviceTypeDefinition)
{
return true;
}
}
var baseType = implementationType.BaseType;
return baseType is not null && baseType.IsGenericTypeDefinition
? baseType.GetGenericTypeDefinition() == serviceTypeDefinition
: baseType is not null && baseType.IsAssignableToGenericTypeDefinition(serviceTypeDefinition);
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
namespace StellaOps.Plugin.DependencyInjection;
public static class StellaOpsPluginRegistration
{
public static IServiceCollection RegisterStellaOpsPlugin(
this IServiceCollection services,
IConfiguration configuration)
{
// No-op today but reserved for future plugin infrastructure services.
return services;
}
}
public sealed class DependencyInjectionRoutine : IDependencyInjectionRoutine
{
public IServiceCollection Register(
IServiceCollection services,
IConfiguration configuration)
{
return services.RegisterStellaOpsPlugin(configuration);
}
}