request/src/request.dispatcher/RequestDispatcherOptions.cs
Louis Seubert e1e02c44a0
All checks were successful
default / dotnet-default-workflow (push) Successful in 2m0s
feat: create request projects for basic CQRS
2026-05-26 22:19:51 +02:00

207 lines
5.4 KiB
C#

// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using System.Collections;
using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
namespace Geekeey.Request.Dispatcher;
internal sealed class RequestDispatcherOptions
{
private readonly List<Type> _search = [];
private readonly Lazy<TypeIndex> _behaviorsTypeIndex;
private readonly Lazy<TypeIndex> _handlersTypeIndex;
public RequestDispatcherOptions()
{
_behaviorsTypeIndex = new Lazy<TypeIndex>(() => new BehaviorTypeIndex(_search.Distinct()));
_handlersTypeIndex = new Lazy<TypeIndex>(() => new HandlerTypeIndex(_search.Distinct()));
}
public void Inspect(IEnumerable<Type> assembly)
{
if (_behaviorsTypeIndex.IsValueCreated || _handlersTypeIndex.IsValueCreated)
{
throw new InvalidOperationException("The type index has already been created. Cannot inspect new assemblies.");
}
_search.AddRange(assembly);
}
public IEnumerable<T> GetRequestBehaviors<T>(IServiceProvider services)
{
return _behaviorsTypeIndex.Value.Resolve<T>(services);
}
public IEnumerable<T> GetRequestHandlers<T>(IServiceProvider services)
{
return _handlersTypeIndex.Value.Resolve<T>(services);
}
private abstract class TypeIndex
{
private readonly ConcurrentDictionary<Type, Func<IServiceProvider, IEnumerable>> _cache = new();
protected readonly Dictionary<Type, List<Type>> _closedTypeInfo = [];
protected readonly List<Type> _openTypeInfo = [];
protected TypeIndex(IEnumerable<Type> collection, Func<Type, bool> predicate)
{
foreach (var type in collection)
{
if (type.IsGenericTypeDefinition)
{
if (type.GetInterfaces().Any(predicate))
{
_openTypeInfo.Add(type);
}
}
else
{
foreach (var @interface in type.GetInterfaces().Where(predicate))
{
(_closedTypeInfo.TryGetValue(@interface, out var list) ? list : _closedTypeInfo[@interface] = []).Add(type);
}
}
}
}
public IEnumerable<T> Resolve<T>(IServiceProvider services)
{
return (IEnumerable<T>)_cache.GetOrAdd(typeof(T), CreateResolverFactory<T>)(services);
}
protected abstract IReadOnlyList<Type> IsAssignableTo(Type type);
private Func<IServiceProvider, IEnumerable<T>> CreateResolverFactory<T>(Type @interface)
{
var list = IsAssignableTo(@interface);
return ResolverFactory;
IEnumerable<T> ResolverFactory(IServiceProvider services)
{
foreach (var type in list)
{
yield return (T)ActivatorUtilities.GetServiceOrCreateInstance(services, type);
}
}
}
}
internal static bool IsRequestHandlerType(Type type)
{
return type.IsGenericType &&
(type.GetGenericTypeDefinition() == typeof(IScalarRequestHandler<,>) ||
type.GetGenericTypeDefinition() == typeof(IStreamRequestHandler<,>));
}
private sealed class HandlerTypeIndex(IEnumerable<Type> collection)
: TypeIndex(collection, IsRequestHandlerType)
{
protected override IReadOnlyList<Type> IsAssignableTo(Type @interface)
{
var result = new List<Type>();
if (_closedTypeInfo.TryGetValue(@interface, out var list))
{
result.AddRange(list);
}
var requestType = @interface.GetGenericArguments()[0];
_ = @interface.GetGenericArguments()[1];
foreach (var type in _openTypeInfo)
{
try
{
// open type case one: Handler<TRequest> : IRequestHandler<TRequest, TOutput>
// We try to close it with the request type.
var impl = type.MakeGenericType(requestType);
if (impl.IsAssignableTo(@interface))
{
result.Add(impl);
}
}
catch (ArgumentException)
{
}
try
{
// open type case two: Handler<T> : IRequestHandler<WrapperRequest<T>, TOutput>
// If the request is generic, we try to close the handler with the request's generic arguments.
if (requestType.IsGenericType)
{
var impl = type.MakeGenericType(requestType.GetGenericArguments());
if (impl.IsAssignableTo(@interface))
{
result.Add(impl);
}
}
}
catch (ArgumentException)
{
}
}
return result;
}
}
internal static bool IsRequestBehaviorType(Type type)
{
return type.IsGenericType &&
(type.GetGenericTypeDefinition() == typeof(IScalarRequestBehavior<,>) ||
type.GetGenericTypeDefinition() == typeof(IStreamRequestBehavior<,>));
}
private sealed class BehaviorTypeIndex(IEnumerable<Type> collection)
: TypeIndex(collection, IsRequestBehaviorType)
{
protected override IReadOnlyList<Type> IsAssignableTo(Type @interface)
{
var result = new List<Type>();
if (_closedTypeInfo.TryGetValue(@interface, out var list))
{
result.AddRange(list);
}
var requestType = @interface.GetGenericArguments()[0];
var responseType = @interface.GetGenericArguments()[1];
foreach (var behaviour in _openTypeInfo)
{
try
{
// open type case one: Behaviour<TRequest, TResponse> : IRequestBehaviour<TRequest, TResponse>
var impl = behaviour.MakeGenericType(requestType, responseType);
if (impl.IsAssignableTo(@interface))
{
result.Add(impl);
}
}
catch (ArgumentException)
{
}
try
{
// open type case two: Behaviour<T> : IRequestBehaviour<WrapperRequest<T>, TResponse>
var impl = behaviour.MakeGenericType(requestType.GetGenericArguments());
if (impl.IsAssignableTo(@interface))
{
result.Add(impl);
}
}
catch (ArgumentException)
{
}
}
return result;
}
}
}