207 lines
5.4 KiB
C#
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|