【问题标题】:Interceptor doesn't work when call a method inside another method in same class在同一类的另一个方法中调用方法时,拦截器不起作用
【发布时间】:2021-12-28 10:17:44
【问题描述】:

我使用Autofac DynamicProxy 来实现方法的缓存。但是当在同一个类的另一个方法中调用一个方法时,inceptor 不起作用。例如,我有 ClassRepository 类:

public class ClassRepository : IClassRepository
{
    [Cache]
    public async Task<List<string>> Method1()
    {
       //Do some thing
    }

    [Cache]
    public async Task<List<string>> Method2()
    {
       var methodCall = await Method1()
    }

}

而我的inceptor是这样的:

public class CacheInterceptor : IInterceptor
{
    private readonly ICache cache;
    private static ConcurrentDictionary<string, bool> InProcessKeys = new ConcurrentDictionary<string, bool>();
    public CacheInterceptor(ICache cache)
    {
        this.cache = cache;
    }
    public void Intercept(IInvocation invocation)
    {
        ProcessInterceptAsync(invocation).Wait();
        
    }

    private async Task ProcessInterceptAsync(IInvocation invocation)
    {
        var proceed = invocation.CaptureProceedInfo();

        

        var cacheAttribute = invocation.MethodInvocationTarget.GetCustomAttributes<CacheAttribute>(false).FirstOrDefault();

        if (cacheAttribute == null)
        {
            proceed.Invoke();
            return;
        }
        var key = GetCacheKey(invocation);
        ExistKeyCheck(key);

        var methodReturnType = invocation.Method.ReturnType;
        dynamic cacheResult = GetCache(key, cacheAttribute, methodReturnType);

        if (cacheResult != null)
        {
            invocation.ReturnValue = cacheResult;
            InProcessKeys.Remove(key, out _);
            return;
        }

        InProcessKeys.TryAdd(key, true);

        proceed.Invoke();
        var delegateType = GetDelegateType(invocation);
        switch (delegateType)
        {
            case MethodType.Synchronous:
                break;
            case MethodType.AsyncAction:
                await InterceptAsync((Task)invocation.ReturnValue);
                break;
            case MethodType.AsyncFunction:
                var method = invocation.MethodInvocationTarget;
                var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
                if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
                {
                    var methodResult = await InterceptAsync((dynamic)invocation.ReturnValue);
                    invocation.ReturnValue = methodResult;
                }
                break;
            default:
                break;
        }

        if (invocation.ReturnValue == null)
        {
            InProcessKeys.Remove(key, out _);
            return;
        }

        await cache.SetAsync(key, invocation.ReturnValue, cacheAttribute.Duration, cacheAttribute.CacheInstance, cacheAttribute.Extend);
        InProcessKeys.Remove(key, out _);
    }

    private dynamic GetCache(string key, CacheAttribute cacheAttribute, Type methodReturnType)
    {
        var finalType = methodReturnType.GetGenericArguments()[0];
        var getMethod = typeof(DistributedCache).GetMethods().Where(x => x.IsGenericMethod && x.Name =="Get").FirstOrDefault().MakeGenericMethod(finalType);
        var cacheResult = (dynamic)getMethod.Invoke(cache, new object[] { key, cacheAttribute.CacheInstance });

        if (cacheResult is null) return null;
        if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>))
            return Task.FromResult(cacheResult);
        else
            return cacheResult;
    }

    private static void ExistKeyCheck(string key)
    {
        if (InProcessKeys.Any(x => x.Key==key))
        {
            Task.Delay(50).Wait();
            var counter = 0;
            while (InProcessKeys.Any(x => x.Key==key) && counter < 10)
            {
                Task.Delay(50).Wait();
                counter++;
            }
        }
    }

    private static async Task InterceptAsync(Task task) => await task.ConfigureAwait(false);

    private static async Task<T> InterceptAsync<T>(Task<T> task) => await task.ConfigureAwait(false);
    private MethodType GetDelegateType(IInvocation invocation)
    {
        var returnType = invocation.Method.ReturnType;
        if (returnType == typeof(Task))
            return MethodType.AsyncAction;
        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
            return MethodType.AsyncFunction;
        return MethodType.Synchronous;
    }
    private enum MethodType
    {
        Synchronous,
        AsyncAction,
        AsyncFunction
    }

    private static string GetCacheKey(IInvocation invocation)
    {

        var key = invocation.Arguments.Length > 0 ? $"-{invocation.Arguments[0]}" : "";

        var cacheKey = $"{invocation.TargetType.FullName.Replace('.', ':')}" +
                       $".{invocation.Method.Name}" +
                       $"{key}";
        return cacheKey;
    }
}

我的autofac模块是这样的:

public class RepositoryModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            
            builder.RegisterType<CacheInterceptor>();
            var flightDataAccess = Assembly.Load("DataAccess");
            builder.RegisterAssemblyTypes(flightDataAccess)
                .Where(x => x.Name.EndsWith("Repository"))
                .AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableInterfaceInterceptors();
                
        }
    }

当我解析 IClassRepository 并调用 Method2 时,CacheInterceptor 为 Method2 成功执行。但是在 Method2 内部,我调用了 Method1,但它不适用于 Method1。如果有人能提供帮助,我将不胜感激。

【问题讨论】:

  • 这两种方法都是接口的一部分吗?
  • 是的,它们都是接口的一部分。我也将这两种方法都更改为virtual。但是拦截器没有发现另一个方法叫做

标签: c# asp.net-core autofac castle-dynamicproxy


【解决方案1】:

你的问题在这里:

public async void Intercept(IInvocation invocation)
...
invocation.Proceed();

您需要开发一个没有async void 的解决方案。看看the docs,它应该会给你一个很好的起点。您需要设置ReturnValue 并调用CaptureProceedInfo

【讨论】:

  • 非常感谢您的努力,我更改了代码并使用了CaptureProceedInfo,但第二种方法仍然存在问题
  • @AmirhosseinYari:请使用更新后的代码更新您的问题。
  • 我更新了我的问题并发布了整个拦截器的所有详细信息
  • @AmirhosseinYari:你不能这样打电话给Wait()。您需要对其进行结构化,以便 Intercept 自然同步,并且唯一的 async 部分位于其任务式结果(同步)分配给 ReturnValue 的方法中。
  • 先尝试让它只使用一个异步返回类型(例如,Task),然后再处理添加其他返回类型。
【解决方案2】:

过了一会儿,我可以解决它。起初我不得不将我的方法更改为virtual 方法。

注意:对于私有方法,我将它们更改为 protected virtual

最后的变化是:

public class ClassRepository : IClassRepository
{
    [Cache]
    public virtual async Task<List<string>> Method1()
    {
       //Do some thing
    }

    [Cache]
    public virtual async Task<List<string>> Method2()
    {
       var methodCall = await Method1()
    }

}

Module 类中,我将EnableInterfaceInterceptors 更改为EnableClassInterceptors,最终更改为:

    public class RepositoryModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            
            builder.RegisterType<CacheInterceptor>();
            var flightDataAccess = Assembly.Load("DataAccess");
            builder.RegisterAssemblyTypes(flightDataAccess)
                .Where(x => x.Name.EndsWith("Repository"))
                .AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableClassInterceptors();
                
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-28
    • 1970-01-01
    相关资源
    最近更新 更多