up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,169 +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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user