Skip to content

Commit

Permalink
Redesigned to resemble core Hangfire
Browse files Browse the repository at this point in the history
1. Redesigned to resemble core Hangfire
2. Resolved bug causing recurring job arguments to be deleted on job editing
3. Resolved issues of the original - bamotav#68, bamotav#71, bamotav#72, bamotav#87
4. Removed cron configurator for the moment - should add one in the future
  • Loading branch information
Serban Apostol committed Feb 17, 2025
1 parent 55efcbb commit 78f2cd6
Show file tree
Hide file tree
Showing 36 changed files with 2,486 additions and 1,139 deletions.
42 changes: 16 additions & 26 deletions src/Hangfire.RecurringJobAdmin/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Hangfire.Annotations;
using Hangfire.Dashboard;
using Hangfire.RecurringJobAdmin.Core;
using Hangfire.RecurringJobAdmin.Dashboard.Content.resx;
using Hangfire.RecurringJobAdmin.Dashboard.Pages;
using Hangfire.RecurringJobAdmin.Pages;
using System;
using System.Linq;
Expand All @@ -10,8 +12,6 @@ namespace Hangfire.RecurringJobAdmin
{
public static class ConfigurationExtensions
{


/// <param name="includeReferences">If is true it will load all dlls references of the current project to find all jobs.</param>
/// <param name="assemblies"></param>
[PublicAPI]
Expand All @@ -38,8 +38,6 @@ public static IGlobalConfiguration UseRecurringJobAdmin(this IGlobalConfiguratio
return config;
}



/// <param name="includeReferences">If is true it will load all dlls references of the current project to find all jobs.</param>
/// <param name="assemblies"></param>
[PublicAPI]
Expand All @@ -66,7 +64,6 @@ public static IGlobalConfiguration UseRecurringJobAdmin(this IGlobalConfiguratio
return config;
}


[PublicAPI]
public static IGlobalConfiguration UseRecurringJobAdmin(this IGlobalConfiguration config)
{
Expand All @@ -79,41 +76,34 @@ private static void CreateManagmentJob()
DashboardRoutes.Routes.AddRazorPage(JobExtensionPage.PageRoute, x => new JobExtensionPage());
DashboardRoutes.Routes.AddRazorPage(JobsStoppedPage.PageRoute, x => new JobsStoppedPage());

DashboardRoutes.Routes.Add("/jobs/GetJobsStopped", new GetJobsStoppedDispatcher());
DashboardRoutes.Routes.Add("/JobConfiguration/GetJobs", new GetJobDispatcher());
DashboardRoutes.Routes.Add("/JobConfiguration/UpdateJobs", new ChangeJobDispatcher());
DashboardRoutes.Routes.Add("/JobConfiguration/GetJob", new GetJobForEdit());
DashboardRoutes.Routes.Add("/JobConfiguration/JobAgent", new JobAgentDispatcher());
DashboardRoutes.Routes.Add("/DataConfiguration/GetTimeZones", new GetTimeZonesDispatcher());

DashboardMetrics.AddMetric(TagDashboardMetrics.JobsStoppedCount);
JobsSidebarMenu.Items.Add(page => new MenuItem("Jobs Stopped", page.Url.To("/jobs/stopped"))
JobsSidebarMenu.Items.Add(page => new MenuItem(RecurringJobAdminStrings.StoppedJobsPage_Title, page.Url.To(JobsStoppedPage.PageRoute))
{
Active = page.RequestPath.StartsWith("/jobs/stopped"),
Active = page.RequestPath.StartsWith(JobsStoppedPage.PageRoute),
Metric = TagDashboardMetrics.JobsStoppedCount,
});

NavigationMenu.Items.Add(page => new MenuItem(JobExtensionPage.Title, page.Url.To("/JobConfiguration"))
NavigationMenu.Items.Add(page => new MenuItem(RecurringJobAdminStrings.JobExtension_Title, page.Url.To(JobExtensionPage.PageRoute))
{
Active = page.RequestPath.StartsWith(JobExtensionPage.PageRoute),
Metric = DashboardMetrics.RecurringJobCount
});

AddDashboardRouteToEmbeddedResource("/JobConfiguration/css/jobExtension", "text/css", "Hangfire.RecurringJobAdmin.Dashboard.Content.css.JobExtension.css");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/css/cron-expression-input", "text/css", "Hangfire.RecurringJobAdmin.Dashboard.Content.css.cron-expression-input.css");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/page", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.jobextension.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/vue", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.vue.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/axio", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.axios.min.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/daysjs", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.daysjs.min.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/relativeTime", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.relativeTime.min.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/vuejsPaginate", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.vuejs-paginate.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/sweetalert", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.sweetalert.js");
AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/cron-expression-input", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.cron-expression-input.js");
//AddDashboardRouteToEmbeddedResource("/JobConfiguration/css/jobExtension", "text/css", "Hangfire.RecurringJobAdmin.Dashboard.Content.css.jobextension.css");
//AddDashboardRouteToEmbeddedResource("/JobConfiguration/css/cron-expression-input", "text/css", "Hangfire.RecurringJobAdmin.Dashboard.Content.css.cron-expression-input.css");
//AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/page", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.jobextension.js");
//AddDashboardRouteToEmbeddedResource("/JobConfiguration/js/cron-expression-input", "application/javascript", "Hangfire.RecurringJobAdmin.Dashboard.Content.js.cron-expression-input.js");

// This seemed to not work. TODO: Investigate if it can be made to work properly. Until then, they are embeded in JobExtensionPage
//var thisAssembly = typeof(ConfigurationExtensions).Assembly;
//DashboardRoutes.AddStylesheetDarkMode(thisAssembly, "Hangfire.RecurringJobAdmin.Dashboard.Content.css.jobextension.css");
//DashboardRoutes.AddJavaScript(thisAssembly, "Hangfire.RecurringJobAdmin.Dashboard.Content.js.jobextension.js");
}

private static void AddDashboardRouteToEmbeddedResource(string route, string contentType, string resourceName)
=> DashboardRoutes.Routes.Add(route, new ContentDispatcher(contentType, resourceName, TimeSpan.FromDays(1)));
//private static void AddDashboardRouteToEmbeddedResource(string route, string contentType, string resourceName)
// => DashboardRoutes.Routes.Add(route, new ContentDispatcher(contentType, resourceName, TimeSpan.FromDays(1)));
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface IRecurringJobRegistry
/// <param name="cron">Cron expressions</param>
/// <param name="timeZone"><see cref="TimeZoneInfo"/></param>
/// <param name="queue">Queue name</param>
void Register(string recurringJobId, MethodInfo method, string cron, TimeZoneInfo timeZone, string queue);
void Register(string recurringJobId, MethodInfo method, object[] argsList, string cron, TimeZoneInfo timeZone, string queue);

}
}
45 changes: 36 additions & 9 deletions src/Hangfire.RecurringJobAdmin/Core/JobAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
using Hangfire.Storage;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;

namespace Hangfire.RecurringJobAdmin.Core
{
Expand Down Expand Up @@ -34,6 +31,19 @@ public static void StopBackgroundJob(string JobId)
transaction.Commit();
}
}
public static void RemoveBackgroundJob(string JobId)
{
using (var connection = JobStorage.Current.GetConnection())
using (connection.AcquireDistributedLock($"lock:recurring-job:{JobId}", TimeSpan.FromSeconds(15)))
using (var transaction = connection.CreateWriteTransaction())
{
transaction.RemoveHash($"recurring-job:{JobId}");
transaction.RemoveFromSet("recurring-jobs", JobId);
transaction.RemoveFromSet(tagStopJob, JobId);

transaction.Commit();
}
}

public static List<PeriodicJob> GetAllJobStopped()
{
Expand All @@ -57,7 +67,9 @@ public static List<PeriodicJob> GetAllJobStopped()
var invocationData = InvocationData.DeserializePayload(payload);
var job = invocationData.DeserializeJob();
dto.Method = job.Method.Name;
dto.Class = job.Type.Name;
dto.Class = job.Type.FullName;
dto.Arguments = job.Args;
dto.ArgumentsTypes = job.Method.GetParameters()?.Select(p => p.ParameterType.FullName);
}
}
catch (JobLoadException ex)
Expand All @@ -74,7 +86,7 @@ public static List<PeriodicJob> GetAllJobStopped()
{
var tempNextExecution = JobHelper.DeserializeNullableDateTime(dataJob["NextExecution"]);

dto.NextExecution = tempNextExecution.HasValue ? tempNextExecution.Value.ChangeTimeZone(dto.TimeZoneId).ToString("G") : "N/A";
dto.NextExecution = tempNextExecution;
}

if (dataJob.ContainsKey("LastJobId") && !string.IsNullOrWhiteSpace(dataJob["LastJobId"]))
Expand All @@ -93,12 +105,17 @@ public static List<PeriodicJob> GetAllJobStopped()
dto.Queue = dataJob["Queue"];
}

if (dataJob.ContainsKey("Cron"))
{
dto.Cron = dataJob["Cron"];
}

if (dataJob.ContainsKey("LastExecution"))
{

var tempLastExecution = JobHelper.DeserializeNullableDateTime(dataJob["LastExecution"]);

dto.LastExecution = tempLastExecution.HasValue ? tempLastExecution.Value.ChangeTimeZone(dto.TimeZoneId).ToString("G") : "N/A";
dto.LastExecution = tempLastExecution;
}

if (dataJob.ContainsKey("CreatedAt"))
Expand All @@ -122,6 +139,18 @@ public static List<PeriodicJob> GetAllJobStopped()
return outPut;
}

public static List<RecurringJobDto> GetStoppedRecurringJobs(IStorageConnection connection)
{
if (connection == null)
{
throw new ArgumentNullException("connection");
}

HashSet<string> allItemsFromSet = connection.GetAllItemsFromSet(tagStopJob);
var getRecurringJobDtos = typeof(Hangfire.Storage.StorageConnectionExtensions).GetMethod("GetRecurringJobDtos", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
return (List<RecurringJobDto>)getRecurringJobDtos?.Invoke(null, new object[] { connection, allItemsFromSet });
}

public static bool IsValidJobId(string jobId, string tag = tagRecurringJob)
{
var result = false;
Expand All @@ -133,7 +162,5 @@ public static bool IsValidJobId(string jobId, string tag = tagRecurringJob)
}
return result;
}


}
}
4 changes: 2 additions & 2 deletions src/Hangfire.RecurringJobAdmin/Core/RecurringJobRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class RecurringJobRegistry : IRecurringJobRegistry
/// <param name="cron">Cron expressions</param>
/// <param name="timeZone"><see cref="TimeZoneInfo"/></param>
/// <param name="queue">Queue name</param>
public void Register(string recurringJobId, MethodInfo method, string cron, TimeZoneInfo timeZone, string queue)
public void Register(string recurringJobId, MethodInfo method, object[] argsList, string cron, TimeZoneInfo timeZone, string queue)
{
if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId));
if (method == null) throw new ArgumentNullException(nameof(method));
Expand All @@ -35,7 +35,7 @@ public void Register(string recurringJobId, MethodInfo method, string cron, Time

for (int i = 0; i < parameters.Length; i++)
{
args[i] = Expression.Default(parameters[i].ParameterType);
args[i] = (argsList?.Length <= i || argsList?[i] == null) ? (Expression)Expression.Default(parameters[i].ParameterType) : (Expression)Expression.Constant(argsList[i], parameters[i].ParameterType);
}

var x = Expression.Parameter(method.DeclaringType, "x");
Expand Down
31 changes: 27 additions & 4 deletions src/Hangfire.RecurringJobAdmin/Core/StorageAssemblySingleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,34 @@ internal void SetCurrentAssembly(bool includeReferences = false, params Assembly

}

public bool IsValidType(string type) => currentAssembly.Any(x => x.GetType(type) != null);

public bool IsValidMethod(string type, string method) => currentAssembly?
.FirstOrDefault(x => x.GetType(type) != null)?.GetType(type)?.GetMethod(method) != null;
public bool IsValidType(string type) => Type.GetType(type) != null || currentAssembly.Any(x => x.GetType(type) != null);

public bool IsValidMethod(string type, string method, Type[] argTypes) => (Type.GetType(type) ?? currentAssembly?
.FirstOrDefault(x => x.GetType(type) != null)?.GetType(type))?.GetMethod(method, argTypes) != null;

public bool AreValidArguments(string type, string method, IEnumerable<object> args, Type[] argTypes)
{
if (!IsValidMethod(type, method, argTypes))
return false;
//var parameters = currentAssembly
// .FirstOrDefault(x => x.GetType(type) != null)
// .GetType(type)
// .GetMethod(method, argTypes)
// .GetParameters();
var argsList = args?.ToList();
if (argTypes?.Count() != argsList?.Count)
{
return false;
}
for (var i = 0; i < argsList.Count; i++)
{
try
{
_ = Convert.ChangeType(argsList[i], argTypes[i]);
}
catch { return false; }
}
return true;
}
}
}
20 changes: 3 additions & 17 deletions src/Hangfire.RecurringJobAdmin/Core/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

namespace Hangfire.RecurringJobAdmin.Core
{



internal static class Utility
{
const string regexCron = @"^\s*($|#|\w+\s*=|(\?|\*|(?:[0-5]?\d)(?:(?:-|\/|\,)(?:[0-5]?\d))?(?:,(?:[0-5]?\d)(?:(?:-|\/|\,)(?:[0-5]?\d))?)*)\s+(\?|\*|(?:[0-5]?\d)(?:(?:-|\/|\,)(?:[0-5]?\d))?(?:,(?:[0-5]?\d)(?:(?:-|\/|\,)(?:[0-5]?\d))?)*)\s+(\?|\*|(?:[01]?\d|2[0-3])(?:(?:-|\/|\,)(?:[01]?\d|2[0-3]))?(?:,(?:[01]?\d|2[0-3])(?:(?:-|\/|\,)(?:[01]?\d|2[0-3]))?)*)\s+(\?|\*|(?:0?[1-9]|[12]\d|3[01])(?:(?:-|\/|\,)(?:0?[1-9]|[12]\d|3[01]))?(?:,(?:0?[1-9]|[12]\d|3[01])(?:(?:-|\/|\,)(?:0?[1-9]|[12]\d|3[01]))?)*)\s+(\?|\*|(?:[1-9]|1[012])(?:(?:-|\/|\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\/|\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\?|\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\s+(\?|\*|(?:[0-6])(?:(?:-|\/|\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\/|\,|#)(?:[0-6]))?(?:L)?)*|\?|\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\s)+(\?|\*|(?:|\d{4})(?:(?:-|\/|\,)(?:|\d{4}))?(?:,(?:|\d{4})(?:(?:-|\/|\,)(?:|\d{4}))?)*))$";
Expand Down Expand Up @@ -50,17 +47,7 @@ public static string MD5(string str)
/// <returns></returns>
public static bool IsValidSchedule(string schedule)
{
try
{
var scheduleLocal = CronExpression.Parse(schedule);

return true; //Regex.IsMatch(schedule, regexCron);
}
catch (Exception)
{

return false;
}
return CronExpression.TryParse(schedule, out _) /*&& Regex.IsMatch(schedule, regexCron)*/;
}

public static string FormatKey(string serverId) => "utilization:" + serverId;
Expand All @@ -71,12 +58,11 @@ public static IEnumerable<Tuple<string, string>> GetTimeZones()
{
#if _WINDOWS

return TimeZoneInfo.GetSystemTimeZones().OrderBy(x=>x.DisplayName).Select(o => new Tuple<string, string>(o.Id, o.DisplayName));
return TimeZoneInfo.GetSystemTimeZones().OrderBy(x => x.DisplayName).Select(o => new Tuple<string, string>(o.Id, o.DisplayName));
#else
return TimeZoneInfo.GetSystemTimeZones().OrderBy(x=>x.Id).Select(o => new Tuple<string, string>(o.Id, o.Id));
return TimeZoneInfo.GetSystemTimeZones().OrderBy(x => x.Id).Select(o => new Tuple<string, string>(o.Id, o.Id));

#endif

}
}
}
Loading

0 comments on commit 78f2cd6

Please sign in to comment.