174 lines
7.8 KiB
C#
174 lines
7.8 KiB
C#
// Copyright (c) The Geekeey Authors
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
|
|
using static Geekeey.Request.Dispatcher.RequestDispatcherOptions;
|
|
|
|
namespace Geekeey.Request.Dispatcher;
|
|
|
|
/// <summary>
|
|
/// Provides extension methods for configuring <see cref="IRequestDispatcherBuilder"/>
|
|
/// with additional capabilities such as searching and registering request handlers in assemblies or adding types directly.
|
|
/// </summary>
|
|
public static class RequestDispatcherBuilderExtensions
|
|
{
|
|
/// <summary>
|
|
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
|
/// configuration.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Prefer the generated <c>Add<Name>(builder)</c> extensions for assemblies that directly reference
|
|
/// <c>Geekeey.Request.Dispatcher</c>. This runtime scanning API remains supported, but nested request handlers are rejected
|
|
/// during registration and generated registration should not be mixed with <see cref="SearchHandlerInAssembly(IRequestDispatcherBuilder, Assembly)"/>
|
|
/// for the same assembly.
|
|
/// </remarks>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
|
/// <param name="assembly">The assembly to search for request handler types.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder SearchHandlerInAssembly(this IRequestDispatcherBuilder builder, Assembly assembly)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
|
|
var exports = assembly.GetTypes()
|
|
.Where(type => type is { IsClass: true, IsAbstract: false })
|
|
.Where(IsRequestHandlerImplementationType);
|
|
|
|
builder.Add(exports);
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
|
/// configuration with the given service lifetime.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Prefer the generated <c>Add<Name>(builder, lifetime)</c> extensions for assemblies that directly reference
|
|
/// <c>Geekeey.Request.Dispatcher</c>. This runtime scanning API remains supported, but nested request handlers are rejected
|
|
/// during registration and generated registration should not be mixed with <see cref="SearchHandlerInAssembly(IRequestDispatcherBuilder, Assembly, ServiceLifetime)"/>
|
|
/// for the same assembly.
|
|
/// </remarks>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
|
/// <param name="assembly">The assembly to search for request handler types.</param>
|
|
/// <param name="lifetime">The lifetime with which the request handlers are registered in the dependency injection container.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder SearchHandlerInAssembly(this IRequestDispatcherBuilder builder, Assembly assembly, ServiceLifetime lifetime)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
|
|
var exports = assembly.GetTypes()
|
|
.Where(type => type is { IsClass: true, IsAbstract: false })
|
|
.Where(IsRequestHandlerImplementationType);
|
|
|
|
builder.Add(exports, lifetime);
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified type to the request dispatcher configuration for inspection.
|
|
/// </summary>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
|
/// <param name="type">The type to be added to the request dispatcher configuration.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
ValidateNoNestedRequestHandlers([type]);
|
|
|
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
|
.Configure(options => options.Inspect([type]));
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified type to the request dispatcher configuration.
|
|
/// This also adds the type to the service collection with the specified lifetime,
|
|
/// allowing it to be resolved as a dependency in request handlers and behaviors.
|
|
/// </summary>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> used to configure the request dispatcher.</param>
|
|
/// <param name="type">The type to be added to the request dispatcher configuration.</param>
|
|
/// <param name="lifetime">The lifetime scope of the type in the service container.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type, ServiceLifetime lifetime)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
ValidateNoNestedRequestHandlers([type]);
|
|
|
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
|
.Configure(options => options.Inspect([type]));
|
|
|
|
builder.Services.Add(new ServiceDescriptor(type, type, lifetime));
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified collection of types to the request dispatcher configuration for inspection.
|
|
/// </summary>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
|
/// <param name="type">The collection of types to be added to the request dispatcher configuration.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, IEnumerable<Type> type)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
|
|
var types = ValidateNoNestedRequestHandlers([.. type]);
|
|
|
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
|
.Configure(options => options.Inspect(types));
|
|
|
|
return builder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified collection of types to the request dispatcher configuration for inspection.
|
|
/// This also adds the specified collection of types to the service collection with the specified lifetime,
|
|
/// allowing it to be resolved as a dependency in request handlers and behaviors.
|
|
/// </summary>
|
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
|
/// <param name="type">The collection of types to be added to the request dispatcher configuration.</param>
|
|
/// <param name="lifetime">The lifetime scope of the types in the service container.</param>
|
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, IEnumerable<Type> type, ServiceLifetime lifetime)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(builder);
|
|
|
|
var types = ValidateNoNestedRequestHandlers([.. type]);
|
|
|
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
|
.Configure(options => options.Inspect(types));
|
|
|
|
builder.Services.Add(types.Select(export => new ServiceDescriptor(export, export, lifetime)));
|
|
|
|
return builder;
|
|
}
|
|
|
|
private static IReadOnlyCollection<Type> ValidateNoNestedRequestHandlers(IReadOnlyCollection<Type> types, [CallerMemberName] string? invoker = null)
|
|
{
|
|
var nestedHandlers = types
|
|
.Where(type => type is { IsClass: true, IsAbstract: false, IsNested: true })
|
|
.Where(IsRequestHandlerImplementationType)
|
|
.Select(type => type.FullName ?? type.Name)
|
|
.OrderBy(static name => name, StringComparer.Ordinal)
|
|
.ToArray();
|
|
|
|
if (nestedHandlers.Length > 0)
|
|
{
|
|
throw new InvalidOperationException($"Nested request handlers are not supported by {invoker}: {string.Join(", ", nestedHandlers)}");
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
private static bool IsRequestHandlerImplementationType(Type type)
|
|
{
|
|
return type.GetInterfaces().Any(IsRequestHandlerType);
|
|
}
|
|
}
|