C# Type Conversion From Object to a Type which Keeps as Type and Determines at the Runtime

62 Views Asked by At

I would like to use an interceptor to intercept some methods on the service layer in my project. Be able to do that I am using Castle Dynamic Proxy and Autofac Aspect Interceptor selector. I am trying to implement a Cache Aspect. This is basically intercept the service method, then gives a parameter which is directly the method name which is being intercepted then also, time as hour.

I am using redis for caching. The return type of the get method of the cashe class is object.

 public object Get(string key, Type type)
        {
            var jsonData = _cache.GetString(key);

            if (jsonData is null)
                return default;

            return JsonSerializer.Deserialize(jsonData, type);
        }

However, Since I was implemented an aspect as attribute, I could only be sure for the return type of the method which will be intercepted on the runtime. The type mismatching leads to exception

The Aspect Class

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);
    }
}

The exception message is : "Unable to cast object of type 'System.Threading.Tasks.Task1[System.Object]' to type 'System.Threading.Tasks.Task1[System.Collections.Generic.IEnumerable`1[Models.Concrete.ResponseModels.Movie.MovieResponse]]'."

However when i write invocation.ReturnValue = Task.FromResult(cachedValue as IEnumerable<MovieResponse>); instead of invocation.ReturnValue = Task.FromResult(cachedValue); everything is ok.

The problem is return value may vary as ; IEnumerable<MovieResponse> or IEnumerable<Movie> or IEnumerable<etc>.

Since I am always work with method names, Casting would not be a problem but as I have said I can only get the return type of the method at the runtime.

One of the method i used on service layer below, (the example returns 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);

}

How could I handle with that type mismatching ?

Edit: I have tried the following

I tried to change invocation.ReturnValue = Task.FromResult(cachedValue); with invocation.ReturnValue = Task.FromResult((dynamic)cachedValue);

However, this time the exception was

"Unable to cast object of type 'System.Threading.Tasks.Task1[System.Collections.Generic.List1[Models.Concrete.ResponseModels.Movie.MovieResponse]]' to type 'System.Threading.Tasks.Task1[System.Collections.Generic.IEnumerable1[Models.Concrete.ResponseModels.Movie.MovieResponse]]'."

It delivers List but the return type was IEnumerable

1

There are 1 best solutions below

0
StriplingWarrior On

One option is to use reflection to get the right generic Task<> type:

invocation.ReturnValue = typeof(Task)
    .GetMethod("FromResult")
    .MakeGenericMethod(returnType)
    .Invoke(null, new object[]{cachedValue});

You might also consider caching the entire Task rather than its result.

var returnValue = invocation.ReturnValue;
TimeSpan span = TimeSpan.FromHours(_duration);
_cacheService.Add(_key, returnValue, span);

Then you can just return that Task directly.

invocation.ReturnValue = cachedValue;

That way, if other invocations happen while the Task is completing, you only need to do the work (invocation.Proceed()) once: everything ends up awaiting the same cached Task. The downside is that if the Task fails, you end up caching the failed Task, and subsequent calls fail rather than trying again. Of course, you can still await the cached task and remove it from the cache in case of failure, to allow future attempts to retry.

_cacheService.Add(_key, returnValue, span);
try
{
    await (Task)returnValue;
}
catch (Exception e)
{
    // ideally log the exception here...
    _cacheService.Remove(_key);
}