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

Версия 1.1 от Alexandr Fokin на 2025/05/11 11:25

 Универсальный перехватчик, Универсальный декоратор.
 
  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 override bool TryInvokeMember(
        InvokeMemberBinder binder,
       object[] args,
       out object result)
    {
       try
        {
           var instance = _subject();
           var method = instance
                .GetType()
                .GetMethod(binder.Name)!;

           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
            {
                result = method.Invoke(instance, args);
            }
           
           return true;
        }
       catch
        {
            result = null;
           return false;
        }
    }

   

   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. При реализации требуется костыль для поддержки асинхронных методов.
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. В декоратор оборачивается интерфейс типа, поэтому если реализация внутри напрямую вызовет свой метод, то он не будет обернут. 
public interface IIntersepterMiddleware
{
    ValueTask BeforeExecuteAsync(
        InvokeMemberBinder binder,
       object[] args);

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

 

 

Теги: