From 449ac340bfcf0d2fbdc0d2051d688e73d930de72 Mon Sep 17 00:00:00 2001 From: Arne Glenstrup Date: Fri, 15 Sep 2023 14:44:49 +0200 Subject: [PATCH] Support job methods returning Task The RecurringJobAdmin plugin now also supports job expressions like `x => x.ExecuteAsync()` that return a task of type `Task` for some given return type `TReturn`. Previously, only tasks of type `Task` were supported. --- .../Core/RecurringJobRegistry.cs | 85 +++++++++++++++---- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/src/Hangfire.RecurringJobAdmin/Core/RecurringJobRegistry.cs b/src/Hangfire.RecurringJobAdmin/Core/RecurringJobRegistry.cs index e54f2d1..4d4a3fc 100644 --- a/src/Hangfire.RecurringJobAdmin/Core/RecurringJobRegistry.cs +++ b/src/Hangfire.RecurringJobAdmin/Core/RecurringJobRegistry.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; +using System.Threading.Tasks; namespace Hangfire.RecurringJobAdmin.Core { @@ -12,7 +12,36 @@ namespace Hangfire.RecurringJobAdmin.Core /// public class RecurringJobRegistry : IRecurringJobRegistry { - + static readonly MethodInfo + addOrUpdateGenericMethod = + typeof(RecurringJob) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single( + m => m.Name == nameof(RecurringJob.AddOrUpdate) && + m.GetGenericArguments().Length == 1 && + m.GetParameters() + .Select(i => i.ParameterType) + .SequenceEqual( + new[] + { + typeof(string), + typeof(string), + typeof(Expression<>) + .MakeGenericType( + typeof(Func<,>) + .MakeGenericType(m.GetGenericArguments()[0], typeof(Task))), + typeof(string), + typeof(RecurringJobOptions) + })); + + static readonly MethodInfo expressionLambdaGenericMethod = + typeof(Expression).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single( + m => m.Name == nameof(Expression.Lambda) && + m.GetGenericArguments().Length == 1 && + m.GetParameters().Select(p => p.ParameterType) + .SequenceEqual(new[] { typeof(Expression), typeof(ParameterExpression[]) })); + /// /// Register RecurringJob via . /// @@ -42,20 +71,42 @@ public void Register(string recurringJobId, MethodInfo method, string cron, Time var methodCall = method.IsStatic ? Expression.Call(method, args) : Expression.Call(x, method, args); - var addOrUpdate = Expression.Call( - typeof(RecurringJob), - nameof(RecurringJob.AddOrUpdate), - new Type[] { method.DeclaringType }, - new Expression[] - { - Expression.Constant(recurringJobId), - Expression.Lambda(methodCall, x), - Expression.Constant(cron), - Expression.Constant(timeZone), - Expression.Constant(queue) - }); - - Expression.Lambda(addOrUpdate).Compile().DynamicInvoke(); + var returnType = method.ReturnType; + var isCallResultGenericTask = + returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>); + if (isCallResultGenericTask) + { + // Dynamic compilation cannot find RecurringJob.AddOrUpdate(..., Expression>, ...) when + // the methodCall argument has type Expression>>, so we construct and invoke + // the call manually: + var jobOptions = new RecurringJobOptions { TimeZone = timeZone }; + var declaringType = method.DeclaringType; + var expressionLambdaMethodReturningNonGenericTask = + expressionLambdaGenericMethod.MakeGenericMethod( + typeof(Func<,>).MakeGenericType(declaringType, typeof(Task))); + var lambdaExpression = + expressionLambdaMethodReturningNonGenericTask.Invoke( + null, new object[] { methodCall, new [] { x } }); + var addOrUpdateMethod = addOrUpdateGenericMethod.MakeGenericMethod(declaringType); + addOrUpdateMethod.Invoke(null, new [] { recurringJobId, queue, lambdaExpression, cron, jobOptions }); + } + else + { + var addOrUpdate = Expression.Call( + typeof(RecurringJob), + nameof(RecurringJob.AddOrUpdate), + new Type[] { method.DeclaringType }, + new Expression[] + { + Expression.Constant(recurringJobId), + Expression.Lambda(methodCall, x), + Expression.Constant(cron), + Expression.Constant(timeZone), + Expression.Constant(queue) + }); + + Expression.Lambda(addOrUpdate).Compile().DynamicInvoke(); + } } /////