我想在我的项目中使用拦截器来拦截服务层的一些方法。 我正在使用 Castle Dynamic Proxy 和 Autofac Aspect Interceptor 选择器来做到这一点。我正在尝试实现缓存方面。这基本上是拦截服务方法,然后给出一个参数,该参数直接是被拦截的方法名称,然后也是时间为小时。
我正在使用redis进行缓存。 cashe类的get方法的返回类型是object。
public object Get(string key, Type type)
{
var jsonData = _cache.GetString(key);
if (jsonData is null)
return default;
return JsonSerializer.Deserialize(jsonData, type);
}
但是,由于我将切面实现为属性,因此我只能确定将在运行时拦截的方法的返回类型。类型不匹配导致异常
Aspect 类
public class CacheAspect : MethodInterception
{
private readonly int _duration;
private readonly string _key;
private readonly ICacheService _cacheService;
public CacheAspect(string key, int duration)
{
_key = key;
_duration = duration;
_cacheService = ServiceTool.ServiceProvider.GetService<ICacheService>();
}
public override async void Intercept(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType.GetGenericArguments()[0];
// I was try to reach of the return type of the method which is called
// I thought that is a type so i can cast at the runtime but i couldnt.
var cachedValue = _cacheService.Get(_key, returnType);
if (cachedValue is not null)
{
invocation.ReturnValue = Task.FromResult(cachedValue); // since cacheValue is object
// it leads to exception
return;
}
invocation.Proceed();
var returnValue = await (dynamic)invocation.ReturnValue;
TimeSpan span = TimeSpan.FromHours(_duration);
_cacheService.Add(_key, returnValue, span);
}
}
异常消息是:“无法转换类型为“System.Threading.Tasks.Task
1[System.Collections.Generic.IEnumerable`1[Models.Concrete.ResponseModels.Movie.MovieResponse]]”的对象。”1[System.Object]' to type 'System.Threading.Tasks.Task
但是,当我写
invocation.ReturnValue = Task.FromResult(cachedValue as IEnumerable<MovieResponse>);
而不是 invocation.ReturnValue = Task.FromResult(cachedValue);
时,一切都很好。
问题是返回值可能会有所不同;
IEnumerable<MovieResponse>
或 IEnumerable<Movie>
或 IEnumerable<etc>
。
由于我总是使用方法名称,因此强制转换不会成为问题,但正如我所说,我只能在运行时获取方法的返回类型。
我在下面的服务层使用的方法之一,(示例返回 IEnumerable)
[CacheAspect(nameof(GetUpcomingMoviesIn30Days), 24)]
public async Task<IEnumerable<MovieResponse>> GetUpcomingMoviesIn30Days(MovieParameters requestParameters)
{
var thirtyDaysFromNow = DateTime.Now.AddDays(30);
var movies = await _repositoryManager.Movie.GetAllMoviesAsync(
m => m.IsReleased.Equals(false)
&& m.ReleaseDate <= thirtyDaysFromNow
&& m.ReleaseDate >= DateTime.Now,
requestParameters,
false);
var mappedResult = _mapper.Map<IEnumerable<MovieResponse>>(movies);
}
我该如何处理这种类型不匹配的情况?
编辑:我已经尝试过以下方法
我尝试改变
invocation.ReturnValue = Task.FromResult(cachedValue);
invocation.ReturnValue = Task.FromResult((dynamic)cachedValue);
不过,这次例外的是
“无法将类型为“System.Threading.Tasks.Task
1[Models.Concrete.ResponseModels.Movie.MovieResponse]]”的对象转换为类型为“System.Threading.Tasks.Task1[System.Collections.Generic.List
1[Models. Concrete.ResponseModels.Movie.MovieResponse]]'。”1[System.Collections.Generic.IEnumerable
它提供了
List
但返回类型是 IEnumerable
一种选择是使用反射来获取正确的通用
Task<>
类型:
invocation.ReturnValue = typeof(Task)
.GetMethod("FromResult")
.MakeGenericMethod(returnType)
.Invoke(null, new object[]{cachedValue});
您还可以考虑缓存整个任务而不是其结果。
var returnValue = invocation.ReturnValue;
TimeSpan span = TimeSpan.FromHours(_duration);
_cacheService.Add(_key, returnValue, span);
然后你可以直接返回该任务。
invocation.ReturnValue = cachedValue;
这样,如果在任务完成时发生其他调用,您只需执行该工作 (
invocation.Proceed()
) 一次:所有内容最终都会 await
相同的缓存 Task
。缺点是,如果任务失败,您最终会缓存失败的任务,并且后续调用会失败,而不是重试。当然,您仍然可以等待缓存的任务,并在失败时将其从缓存中删除,以允许将来的尝试重试。
_cacheService.Add(_key, returnValue, span);
try
{
await (Task)returnValue;
}
catch (Exception e)
{
// ideally log the exception here...
_cacheService.Remove(_key);
}