Универсальный перехватчик

Редактировал(а) Alexandr Fokin 2025/05/11 11:45

 Универсальный перехватчик, Универсальный декоратор.
 
  1. Используя функционал DLR можно реализовать перехватчик, оборачивающий любой метод.
    1. Библиотека ImpromptuInterface предоставляет функционал создания обертки над DynamicObject, которую является реализацией интерфейса (не наследую его явно).
  2. Используя функционал для декорирования (например Scrutor) при регистрации сервисов в DI можно автоматически оборачивать компоненты (сервисы) в универсальный декоратор.
    1. Таким образом можно реализовать АОП подход без использования MediatR.
 

Набросок

public class UniversalIntersepter
    : DynamicObject
{
   private readonly Func<object> _subject;
   private readonly IIntersepterMiddleware _intersepterMiddleware;

   public UniversalIntersepter(
       object subject,
        IIntersepterMiddleware intersepterMiddleware)
    {
        _subject = () => subject;
        _intersepterMiddleware = intersepterMiddleware;
    }

   public UniversalIntersepter(Func<object> subjectFactory)
    {
        _subject = subjectFactory;
    }

   public override bool TryInvokeMember(
        InvokeMemberBinder binder,
       object[] args,
       out object result)
    {
       var instance = _subject();
       var method = instance
            .GetType()
           // TODO: не учитывает перегрузки.
           .GetMethod(binder.Name)!;

       if (method == null)
        {
            result = default!;
           return false;
        }

       if (method.ReturnType == typeof(Task))
        {
            result = new TaskHandler(this, instance, method, binder, args)
                .HandleAsync();
        }
       else if (method.ReturnType.BaseType == typeof(Task))
        {
           var genericWrapperType = typeof(TaskHandler<>)
                .MakeGenericType(method.ReturnType.GenericTypeArguments[0]);
           var wrapper = Activator.CreateInstance(genericWrapperType, this, instance, method, binder, args);

            result = wrapper.GetType()
                .GetMethod(nameof(TaskHandler.HandleAsync))
                .Invoke(wrapper, null);
        }
       else if (method.ReturnType == typeof(ValueTask))
        {
            result = new ValueTaskTaskHandler(this, instance, method, binder, args)
                .HandleAsync();
        }
       else if (method.ReturnType.BaseType == typeof(ValueTask))
        {
           var genericWrapperType = typeof(ValueTaskHandler<>)
                .MakeGenericType(method.ReturnType.GenericTypeArguments[0]);
           var wrapper = Activator.CreateInstance(genericWrapperType, this, instance, method, binder, args);

            result = wrapper.GetType()
                .GetMethod(nameof(TaskHandler.HandleAsync))
                .Invoke(wrapper, null);
        }
       else
        {
            _intersepterMiddleware.BeforeExecute(binder, args);
            result = method.Invoke(instance, args);
            _intersepterMiddleware.AfterExecute(binder, args, result);
        }

       return true;
    }
   

   public object ToType(Type interfaceType)
    {
       var result = Impromptu.DynamicActLike(this, interfaceType);
       return result;
    }

   private class TaskHandler
    {
       private readonly UniversalIntersepter _universalIntersepter;
       private readonly object _subject;
       private readonly MethodInfo _methodInfo;
       private readonly InvokeMemberBinder _binder;
       private readonly object[] _args;

       public TaskHandler(
            UniversalIntersepter universalIntersepter,
           object subject,
            MethodInfo methodInfo,
            InvokeMemberBinder binder,
           object[] args)
        {
            _universalIntersepter = universalIntersepter;
            _subject = subject;
            _methodInfo = methodInfo;
            _binder = binder;
            _args = args;
        }

       public async Task HandleAsync()
        {
           await _universalIntersepter._intersepterMiddleware.BeforeExecuteAsync(_binder, _args);
           await (Task)_methodInfo.Invoke(_subject, _args);
           await _universalIntersepter._intersepterMiddleware.AfterExecuteAsync(_binder, _args, null);
        }
    }

   private class ValueTaskTaskHandler
    {
       private readonly UniversalIntersepter _universalIntersepter;
       private readonly object _subject;
       private readonly MethodInfo _methodInfo;
       private readonly InvokeMemberBinder _binder;
       private readonly object[] _args;

       public ValueTaskTaskHandler(
            UniversalIntersepter universalIntersepter,
           object subject,
            MethodInfo methodInfo,
            InvokeMemberBinder binder,
           object[] args)
        {
            _universalIntersepter = universalIntersepter;
            _subject = subject;
            _methodInfo = methodInfo;
            _binder = binder;
            _args = args;
        }

       public async ValueTask HandleAsync()
        {
           await _universalIntersepter._intersepterMiddleware.BeforeExecuteAsync(_binder, _args);
           await (ValueTask)_methodInfo.Invoke(_subject, _args);
           await _universalIntersepter._intersepterMiddleware.AfterExecuteAsync(_binder, _args, null);
        }
    }

   private class TaskHandler<T>
    {
       private readonly UniversalIntersepter _universalIntersepter;
       private readonly object _subject;
       private readonly MethodInfo _methodInfo;
       private readonly InvokeMemberBinder _binder;
       private readonly object[] _args;

       public TaskHandler(
            UniversalIntersepter universalIntersepter,
           object subject,
            MethodInfo methodInfo,
            InvokeMemberBinder binder,
           object[] args)
        {
            _universalIntersepter = universalIntersepter;
            _subject = subject;
            _methodInfo = methodInfo;
            _binder = binder;
            _args = args;
        }

       public async Task<T> HandleAsync()
        {
           await _universalIntersepter._intersepterMiddleware.BeforeExecuteAsync(_binder, _args);
           var result = await (Task<T>)_methodInfo.Invoke(_subject, _args);
           await _universalIntersepter._intersepterMiddleware.AfterExecuteAsync(_binder, _args, result);

           return result;
        }
    }

   private class ValueTaskHandler<T>
    {
       private readonly UniversalIntersepter _universalIntersepter;
       private readonly object _subject;
       private readonly MethodInfo _methodInfo;
       private readonly InvokeMemberBinder _binder;
       private readonly object[] _args;

       public ValueTaskHandler(
            UniversalIntersepter universalIntersepter,
           object subject,
            MethodInfo methodInfo,
            InvokeMemberBinder binder,
           object[] args)
        {
            _universalIntersepter = universalIntersepter;
            _subject = subject;
            _methodInfo = methodInfo;
            _binder = binder;
            _args = args;
        }

       public async ValueTask<T> HandleAsync()
        {
           await _universalIntersepter._intersepterMiddleware.BeforeExecuteAsync(_binder, _args);
           var result = await (ValueTask<T>)_methodInfo.Invoke(_subject, _args);
           await _universalIntersepter._intersepterMiddleware.AfterExecuteAsync(_binder, _args, result);

           return result;
        }
    }
}
  1. Это набросок, показывающий сам смысл.
    1. Можно оптимизировать работу с рефлексией.
  2. _intersepterMiddleware представить как IReadonlyList<IIntersepterMiddleware >
  3. При реализации требуется костыль для поддержки асинхронных методов.
    1. Если оборачиваемый метод синхронный, то возможен только синхронный декоратор.
public static class ServiceCollectionExtension
{
   public static IServiceCollection WrapUniversalDecorator(
       this IServiceCollection services,
        Func<ServiceDescriptor, bool>? needWrapAction = null)
    {
        needWrapAction = needWrapAction ?? ((_) => true);

       foreach (var elem in services.Select(e => e).ToArray())
        {
           // Работает только для интерфейсов.
           if (!elem.ServiceType.IsInterface)
            {
               continue;
            }

           // Не оборачиваем сам middleware
           if (elem.ServiceType == typeof(IIntersepterMiddleware))
            {
               continue;
            }

           if (!needWrapAction(elem))
            {
               continue;
            }

           // Оборачиваем в универсальный декоратор.
           services.Decorate(
                serviceType: elem.ServiceType,
                decorator:
                    (e, s) => new UniversalIntersepter(
                        e,
                        s.GetRequiredService<IIntersepterMiddleware>()
                        )
                        .ToType(elem.ServiceType)                    
                        );
        }
       
       return services;
    }
  1. Декорируются только интерфейсы, не строгие типы.
  2. В декоратор оборачивается интерфейс типа, поэтому если реализация внутри напрямую вызовет свой метод, то он не будет обернут. 
ValueTask BeforeExecuteAsync(
    InvokeMemberBinder binder,
   object[] args);

void BeforeExecute(
    InvokeMemberBinder binder,
   object[] args);

ValueTask AfterExecuteAsync(
    InvokeMemberBinder binder,
   object[] args,
   object? result);

void AfterExecute(
    InvokeMemberBinder binder,
   object[] args,
   object? result);
Можно переделать на формат middleware, когда идет один метод Execute, принимающий nextHandler.
Пока что для простоты не запаривался.

 

Теги: