diff --git a/Hangfire.RecurringJobExtensions.sln b/Hangfire.RecurringJobExtensions.sln index 2128416..5eb295e 100644 --- a/Hangfire.RecurringJobExtensions.sln +++ b/Hangfire.RecurringJobExtensions.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Samples", "samples EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.RecurringJobExtensions.Tests", "test\Hangfire.RecurringJobExtensions.Tests\Hangfire.RecurringJobExtensions.Tests.csproj", "{B1AA8C91-161A-4A92-9B0A-B47742CDDB38}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.WebApi", "Hangfire.WebApi\Hangfire.WebApi.csproj", "{8D5930B9-B337-4722-86C0-F9229AC2960F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {B1AA8C91-161A-4A92-9B0A-B47742CDDB38}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1AA8C91-161A-4A92-9B0A-B47742CDDB38}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1AA8C91-161A-4A92-9B0A-B47742CDDB38}.Release|Any CPU.Build.0 = Release|Any CPU + {8D5930B9-B337-4722-86C0-F9229AC2960F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D5930B9-B337-4722-86C0-F9229AC2960F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D5930B9-B337-4722-86C0-F9229AC2960F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D5930B9-B337-4722-86C0-F9229AC2960F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -47,5 +53,9 @@ Global {D75DFE5D-38CF-4150-8264-EB22E579237D} = {6E0E1A07-7311-4F8C-B1BD-B3803B6E621E} {9DCBF5BA-414C-4E39-B264-CFD6C824AA66} = {B04B5FCE-89A8-4A3E-9671-CB3070C89AAF} {B1AA8C91-161A-4A92-9B0A-B47742CDDB38} = {F5091E02-C3A4-407E-995C-86E07A801832} + {8D5930B9-B337-4722-86C0-F9229AC2960F} = {B04B5FCE-89A8-4A3E-9671-CB3070C89AAF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C54AE9A2-40D8-41BF-8974-E0F16F4AB0C2} EndGlobalSection EndGlobal diff --git a/Hangfire.WebApi/Controllers/ValuesController.cs b/Hangfire.WebApi/Controllers/ValuesController.cs new file mode 100644 index 0000000..c31cbed --- /dev/null +++ b/Hangfire.WebApi/Controllers/ValuesController.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Hangfire.RecurringJobExtensions.Manager; +using Hangfire.WebApi.TestJob; +using Microsoft.AspNetCore.Mvc; + +namespace Hangfire.WebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + private readonly IScheduleJobManager ScheduleJobManager; + + public ValuesController(IScheduleJobManager ScheduleJobManager) + { + this.ScheduleJobManager = ScheduleJobManager; + } + + [HttpPost("Schedule")] + public async void Schedule([FromBody] object request) + { + var args1 = new SimpleJobArgs1() + { + //Name = request.Name, + //Data = new SimpleJobArgsData1 { MerNo = request.Data.MerNo, OrderNo = request.Data.OrderNo } + }; + + var args2 = new SimpleJobArgs2() + { + //Name = request.Name, + //Data = new SimpleJobArgsData2 { MerNo = request.Data.MerNo, OrderNo = request.Data.OrderNo } + }; + + await ScheduleJobManager.ExecuteScheduleAsync(args1); + await ScheduleJobManager.ExecuteBackgroundJobAsync(null, TimeSpan.FromSeconds(55), args2); + //https://github.com/HangfireIO/Hangfire/issues/1168 + } + + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} \ No newline at end of file diff --git a/Hangfire.WebApi/Hangfire.WebApi.csproj b/Hangfire.WebApi/Hangfire.WebApi.csproj new file mode 100644 index 0000000..81be1db --- /dev/null +++ b/Hangfire.WebApi/Hangfire.WebApi.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/Hangfire.WebApi/Program.cs b/Hangfire.WebApi/Program.cs new file mode 100644 index 0000000..905a2e2 --- /dev/null +++ b/Hangfire.WebApi/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Hangfire.WebApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/Hangfire.WebApi/Properties/launchSettings.json b/Hangfire.WebApi/Properties/launchSettings.json new file mode 100644 index 0000000..755fbbc --- /dev/null +++ b/Hangfire.WebApi/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:18803", + "sslPort": 44343 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Hangfire.WebApi": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Hangfire.WebApi/Startup.cs b/Hangfire.WebApi/Startup.cs new file mode 100644 index 0000000..81d392c --- /dev/null +++ b/Hangfire.WebApi/Startup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Hangfire.WebApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/Hangfire.WebApi/TestJob/SimpleJob.cs b/Hangfire.WebApi/TestJob/SimpleJob.cs new file mode 100644 index 0000000..a083147 --- /dev/null +++ b/Hangfire.WebApi/TestJob/SimpleJob.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Hangfire.RecurringJobExtensions; +using Hangfire.RecurringJobExtensions.Manager; +using Hangfire.Server; +using Newtonsoft.Json; + +namespace Hangfire.WebApi.TestJob +{ + public class SimpleJob : IBackgroundJob + { + [RecurringJob("*/3 * * * *")]//At every 3th minute. + public Task Execute(PerformContext context) + { + var ctx = context as PerformContext; + var data = ctx.GetJobData(); + + Debug.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {JsonConvert.SerializeObject(data)} Running ..."); + return Task.CompletedTask; + } + } + + public class SimpleJobArgs1 : IJobData + { + public string Name { get; set; } + public SimpleJobArgsData1 Data { get; set; } + public string OrderNo { get; set; } + } + + public class SimpleJobArgsData1 + { + public string OrderNo { get; set; } + public string MerNo { get; set; } + } +} \ No newline at end of file diff --git a/Hangfire.WebApi/TestJob/SimpleJob2.cs b/Hangfire.WebApi/TestJob/SimpleJob2.cs new file mode 100644 index 0000000..5ea2e70 --- /dev/null +++ b/Hangfire.WebApi/TestJob/SimpleJob2.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Hangfire.RecurringJobExtensions.Manager; +using Hangfire.Server; + +namespace Hangfire.WebApi.TestJob +{ + public class SimpleJob2 : IBackgroundJob + { + public Task Execute(PerformContext context, params SimpleJobArgs2[] args) + { + var ctx = context as PerformContext; + + Debug.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {args[0].Name}'s SimpleJobArgs2 (ExecuteBackgroundJobAsync) is Running ..."); + return Task.CompletedTask; + } + } + + public class SimpleJobArgs2 : IJobData + { + public string Name { get; set; } + public SimpleJobArgsData2 Data { get; set; } + public string OrderNo { get; set; } + } + + public class SimpleJobArgsData2 + { + public string OrderNo { get; set; } + public string MerNo { get; set; } + } +} \ No newline at end of file diff --git a/Hangfire.WebApi/appsettings.Development.json b/Hangfire.WebApi/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/Hangfire.WebApi/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Hangfire.WebApi/appsettings.json b/Hangfire.WebApi/appsettings.json new file mode 100644 index 0000000..def9159 --- /dev/null +++ b/Hangfire.WebApi/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/README.md b/README.md index 6f83109..625f133 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# Hangfire.RecurringJobExtensions Fork Version +--With encapsulation of the the interface ScheduleJobManager, which support DI. Like following: +```csharp + await ScheduleJobManager.ExecuteScheduleAsync(args1); + await ScheduleJobManager.ExecuteBackgroundJobAsync(null, TimeSpan.FromSeconds(55), args2); +``` # Hangfire.RecurringJobExtensions [![Official Site](https://img.shields.io/badge/site-hangfire.io-blue.svg)](http://hangfire.io) diff --git a/appveyor.yml b/appveyor.yml index 3089906..15cf324 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,4 +7,5 @@ test_script: - cmd: >- cd test\Hangfire.RecurringJobExtensions.Tests - dotnet test \ No newline at end of file + dotnet test +image: Visual Studio 2017 \ No newline at end of file diff --git a/samples/Hangfire.Samples/Hangfire.Samples.csproj b/samples/Hangfire.Samples/Hangfire.Samples.csproj index 6d20cd3..ae39f7b 100644 --- a/samples/Hangfire.Samples/Hangfire.Samples.csproj +++ b/samples/Hangfire.Samples/Hangfire.Samples.csproj @@ -1,13 +1,12 @@ - + - netcoreapp1.1 + netcoreapp2.1 true Hangfire.Samples Exe Hangfire.Samples - 1.1.1 - $(PackageTargetFallback);dotnet5.6;portable-net45+win8 + 2.0.0 @@ -16,10 +15,6 @@ - - - - @@ -32,4 +27,8 @@ + + + + diff --git a/samples/Hangfire.Samples/RecurringJobService.cs b/samples/Hangfire.Samples/RecurringJobService.cs index e15597e..abdd617 100644 --- a/samples/Hangfire.Samples/RecurringJobService.cs +++ b/samples/Hangfire.Samples/RecurringJobService.cs @@ -5,35 +5,38 @@ namespace Hangfire.Samples { - public class RecurringJobService - { - [RecurringJob("*/1 * * * *")] - [Queue("jobs")] - public void TestJob1(PerformContext context) - { - context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob1 Running ..."); - } - [RecurringJob("*/2 * * * *", RecurringJobId = "TestJob2")] - [Queue("jobs")] - public void TestJob2(PerformContext context) - { - context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob2 Running ..."); - } - [RecurringJob("*/2 * * * *", "China Standard Time", "jobs")] - public void TestJob3(PerformContext context) - { - context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob3 Running ..."); - } - [RecurringJob("*/5 * * * *", "jobs")] - public void InstanceTestJob(PerformContext context) - { - context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} InstanceTestJob Running ..."); - } + public class RecurringJobService + { + [RecurringJob("*/1 * * * *")] + [Queue("jobs")] + public void TestJob1(PerformContext context) + { + context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob1 Running ..."); + } - [RecurringJob("*/6 * * * *", "UTC", "jobs")] - public static void StaticTestJob(PerformContext context) - { - context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} StaticTestJob Running ..."); - } - } -} + [RecurringJob("*/2 * * * *", RecurringJobId = "TestJob2")] + [Queue("jobs")] + public void TestJob2(PerformContext context) + { + context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob2 Running ..."); + } + + [RecurringJob("*/2 * * * *", "China Standard Time", "jobs")] + public void TestJob3(PerformContext context) + { + context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} TestJob3 Running ..."); + } + + [RecurringJob("*/5 * * * *", "jobs")] + public void InstanceTestJob(PerformContext context) + { + context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} InstanceTestJob Running ..."); + } + + [RecurringJob("*/6 * * * *", "UTC", "jobs")] + public static void StaticTestJob(PerformContext context) + { + context.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} StaticTestJob Running ..."); + } + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/CronJob.cs b/src/Hangfire.RecurringJobExtensions/CronJob.cs index 033ac1d..9333554 100644 --- a/src/Hangfire.RecurringJobExtensions/CronJob.cs +++ b/src/Hangfire.RecurringJobExtensions/CronJob.cs @@ -1,111 +1,158 @@ -using System; +using Hangfire.RecurringJobExtensions.Configuration; +using Hangfire.RecurringJobExtensions.Helpers; +using Hangfire.States; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Hangfire.RecurringJobExtensions.Configuration; +using System.Linq.Expressions; +using System.Reflection; namespace Hangfire.RecurringJobExtensions { - /// - /// The helper class to build automatically. - /// - public class CronJob - { - /// - /// Builds automatically within specified interface or class. - /// - /// Specified interface or class - public static void AddOrUpdate(params Type[] types) - { - AddOrUpdate(() => types); - } - - /// - /// Builds automatically within specified interface or class. - /// - /// The provider to get specified interfaces or class. - public static void AddOrUpdate(Func> typesProvider) - { - if (typesProvider == null) throw new ArgumentNullException(nameof(typesProvider)); - - IRecurringJobBuilder builder = new RecurringJobBuilder(); - - builder.Build(typesProvider); - } - - /// - /// Builds automatically by using multiple JSON configuration files. - /// - /// The array of json files. - /// Whether the should be reloaded if the file changes. - public static void AddOrUpdate(string[] jsonFiles, bool reloadOnChange = true) - { - if (jsonFiles == null) throw new ArgumentNullException(nameof(jsonFiles)); - - foreach (var jsonFile in jsonFiles) - AddOrUpdate(jsonFile, reloadOnChange); - } - /// - /// Builds automatically by using a JSON configuration. - /// - /// Json file for configuration. - /// Whether the should be reloaded if the file changes. - public static void AddOrUpdate(string jsonFile, bool reloadOnChange = true) - { - if (string.IsNullOrWhiteSpace(jsonFile)) throw new ArgumentNullException(nameof(jsonFile)); - - var configFile = File.Exists(jsonFile) ? jsonFile : - Path.Combine( + /// + /// The helper class to build automatically. + /// + public class CronJob + { + /// + /// Builds automatically within specified interface or class. + /// + /// Specified interface or class + public static void AddOrUpdate(params Type[] types) + { + AddOrUpdate(() => types); + } + + /// + /// Builds automatically within specified interface or class. + /// + /// The provider to get specified interfaces or class. + public static void AddOrUpdate(Func> typesProvider) + { + if (typesProvider == null) throw new ArgumentNullException(nameof(typesProvider)); + + IRecurringJobBuilder builder = new RecurringJobBuilder(); + + builder.Build(typesProvider); + } + + /// + /// Builds automatically by using multiple JSON configuration files. + /// + /// The array of json files. + /// Whether the should be reloaded if the file changes. + public static void AddOrUpdate(string[] jsonFiles, bool reloadOnChange = true) + { + if (jsonFiles == null) throw new ArgumentNullException(nameof(jsonFiles)); + + foreach (var jsonFile in jsonFiles) + AddOrUpdate(jsonFile, reloadOnChange); + } + + /// + /// Builds automatically by using a JSON configuration. + /// + /// Json file for configuration. + /// Whether the should be reloaded if the file changes. + public static void AddOrUpdate(string jsonFile, bool reloadOnChange = true) + { + if (string.IsNullOrWhiteSpace(jsonFile)) throw new ArgumentNullException(nameof(jsonFile)); + + var configFile = File.Exists(jsonFile) ? jsonFile : + Path.Combine( #if NET45 AppDomain.CurrentDomain.BaseDirectory, #else - AppContext.BaseDirectory, + AppContext.BaseDirectory, #endif - jsonFile); - - - if (!File.Exists(configFile)) throw new FileNotFoundException($"The json file {configFile} does not exist."); - - IConfigurationProvider provider = new JsonConfigurationProvider(configFile, reloadOnChange); - - AddOrUpdate(provider); - } - - /// - /// Builds automatically with . - /// - /// - public static void AddOrUpdate(IConfigurationProvider provider) - { - if (provider == null) throw new ArgumentNullException(nameof(provider)); - - IRecurringJobBuilder builder = new RecurringJobBuilder(); - - AddOrUpdate(provider.Load()); - } - - /// - /// Builds automatically with the collection of . - /// - /// The collection of . - public static void AddOrUpdate(IEnumerable recurringJobInfos) - { - if (recurringJobInfos == null) throw new ArgumentNullException(nameof(recurringJobInfos)); - - IRecurringJobBuilder builder = new RecurringJobBuilder(); - - builder.Build(() => recurringJobInfos); - } - - /// - /// Builds automatically with the array of . - /// - /// The array of . - public static void AddOrUpdate(params RecurringJobInfo[] recurringJobInfos) - { - if (recurringJobInfos == null) throw new ArgumentNullException(nameof(recurringJobInfos)); - - AddOrUpdate(recurringJobInfos.AsEnumerable()); - } - } -} + jsonFile); + + if (!File.Exists(configFile)) throw new FileNotFoundException($"The json file {configFile} does not exist."); + + IConfigurationProvider provider = new JsonConfigurationProvider(configFile, reloadOnChange); + + AddOrUpdate(provider); + } + + /// + /// Builds automatically with . + /// + /// + public static void AddOrUpdate(IConfigurationProvider provider) + { + if (provider == null) throw new ArgumentNullException(nameof(provider)); + + IRecurringJobBuilder builder = new RecurringJobBuilder(); + + AddOrUpdate(provider.Load()); + } + + /// + /// Builds automatically with the collection of . + /// + /// The collection of . + public static void AddOrUpdate(IEnumerable recurringJobInfos) + { + if (recurringJobInfos == null) throw new ArgumentNullException(nameof(recurringJobInfos)); + + IRecurringJobBuilder builder = new RecurringJobBuilder(); + + builder.Build(() => recurringJobInfos); + } + + /// + /// Builds automatically with the array of . + /// + /// The array of . + public static void AddOrUpdate(params RecurringJobInfo[] recurringJobInfos) + { + if (recurringJobInfos == null) throw new ArgumentNullException(nameof(recurringJobInfos)); + + AddOrUpdate(recurringJobInfos.AsEnumerable()); + } + + /// Adds an or update to 'args'. + /// Thrown when one or more required arguments are + /// null. + /// Generic type parameter. + /// The expression. + /// The arguments. + public static void AddOrUpdate(Expression> expression, object args) + { + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + foreach (var method in typeof(T).GetTypeInfo().DeclaredMethods) + { + if (!method.IsDefined(typeof(RecurringJobAttribute), false)) continue; + + var attribute = method.GetCustomAttribute(false); + + if (attribute == null) continue; + + if (string.IsNullOrWhiteSpace(attribute.RecurringJobId)) + { + attribute.RecurringJobId = method.GetRecurringJobId(); + } + + if (!attribute.Enabled) + { + RecurringJob.RemoveIfExists(attribute.RecurringJobId); + continue; + } + + var recurringJobInfo = new RecurringJobInfo + { + RecurringJobId = attribute.RecurringJobId, + Enable = true, + Method = expression.GetMethodInfo(), + JobData = args.ToDictionary(), + Cron = attribute.Cron, + TimeZone = string.IsNullOrEmpty(attribute.TimeZone) ? TimeZoneInfo.Utc : TimeZoneInfo.FindSystemTimeZoneById(attribute.TimeZone), + Queue = attribute.Queue ?? EnqueuedState.DefaultQueue + }; + AddOrUpdate(recurringJobInfo); + } + } + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Hangfire.RecurringJobExtensions.csproj b/src/Hangfire.RecurringJobExtensions/Hangfire.RecurringJobExtensions.csproj index cdcf294..60dfbcf 100644 --- a/src/Hangfire.RecurringJobExtensions/Hangfire.RecurringJobExtensions.csproj +++ b/src/Hangfire.RecurringJobExtensions/Hangfire.RecurringJobExtensions.csproj @@ -5,7 +5,7 @@ Hangfire.RecurringJobExtensions 1.1.6 icsharp - netstandard1.3;net45 + netstandard2.0;net45 true true Hangfire.RecurringJobExtensions @@ -21,11 +21,16 @@ false + + TRACE;DEBUG;NETSTANDARD; + AnyCPU + + - + @@ -33,4 +38,6 @@ + + diff --git a/src/Hangfire.RecurringJobExtensions/Helpers/MethodInfoHelper.cs b/src/Hangfire.RecurringJobExtensions/Helpers/MethodInfoHelper.cs new file mode 100644 index 0000000..673ab52 --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Helpers/MethodInfoHelper.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace Hangfire.RecurringJobExtensions.Helpers +{ + /// A method information helper. + public static class MethodInfoHelper + { + /// + /// Given a lambda expression that calls a method, returns the method info. + /// + /// The expression. + /// + public static MethodInfo GetMethodInfo(this Expression expression) + { + return GetMethodInfo((LambdaExpression)expression); + } + + /// + /// Given a lambda expression that calls a method, returns the method info. + /// + /// + /// The expression. + /// + public static MethodInfo GetMethodInfo(this Expression> expression) + { + return GetMethodInfo((LambdaExpression)expression); + } + + /// Given a lambda expression that calls a method, returns the method info. + /// . + /// Type of the result. + /// The expression. + /// The method information. + public static MethodInfo GetMethodInfo(this Expression> expression) + { + return GetMethodInfo((LambdaExpression)expression); + } + + /// + /// Given a lambda expression that calls a method, returns the method info. + /// + /// The expression. + /// + public static MethodInfo GetMethodInfo(this LambdaExpression expression) + { + MethodCallExpression outermostExpression = expression.Body as MethodCallExpression; + + if (outermostExpression == null) + { + throw new ArgumentException("Invalid Expression. Expression should consist of a Method call only."); + } + + return outermostExpression.Method; + } + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Helpers/ObjectToDictionaryHelper.cs b/src/Hangfire.RecurringJobExtensions/Helpers/ObjectToDictionaryHelper.cs new file mode 100644 index 0000000..a426c45 --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Helpers/ObjectToDictionaryHelper.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Dynamic; + +namespace Hangfire.RecurringJobExtensions.Helpers +{ + /// An object to dictionary helper. + public static class ObjectToDictionaryHelper + { + //private static readonly MethodInfo AddToDicitonaryMethod = typeof(IDictionary).GetMethod("Add"); + //private static readonly ConcurrentDictionary>> Converters = new ConcurrentDictionary>>(); + //private static readonly ConstructorInfo DictionaryConstructor = typeof(Dictionary).GetConstructors().FirstOrDefault(c => c.IsPublic && !c.GetParameters().Any()); + + ////todo : Has bug + //public static IDictionary ToDictionaryEnhance(this object obj) + //{ + // if (obj == null) + // return null; + // else + // return Converters.GetOrAdd(obj.GetType(), o => + // { + // var outputType = typeof(IDictionary); + // var inputType = obj.GetType(); + // var inputExpression = Expression.Parameter(typeof(object), "input"); + // var typedInputExpression = Expression.Convert(inputExpression, inputType); + // var outputVariable = Expression.Variable(outputType, "output"); + // var returnTarget = Expression.Label(outputType); + // var body = new List + // { + // Expression.Assign(outputVariable, Expression.New(DictionaryConstructor)) + // }; + // body.AddRange( + // from prop in inputType.GetProperties( + // BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) + // where prop.CanRead && (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string)) + // let getExpression = Expression.Property(typedInputExpression, prop.GetMethod) + // select Expression.Call(outputVariable, AddToDicitonaryMethod, Expression.Constant(prop.Name), + // getExpression)); + // body.Add(Expression.Return(returnTarget, outputVariable)); + // body.Add(Expression.Label(returnTarget, Expression.Constant(null, outputType))); + + // var lambdaExpression = Expression.Lambda>>( + // Expression.Block(new[] { outputVariable }, body), + // inputExpression); + + // return lambdaExpression.Compile(); + // })(obj); + //} + + /// An object extension method that converts a source to a dictionary. + /// The source to act on. + /// Source as an IDictionary<string,object> + public static IDictionary ToDictionary(this object source) + { + return source.ToDictionary(); + } + + /// An object extension method that converts a source to a dictionary. + /// Generic type parameter. + /// The source to act on. + /// Source as an IDictionary<string,object> + public static IDictionary ToDictionary(this object source) + { + if (source == null) ThrowExceptionWhenSourceArgumentIsNull(); + + var dictionary = new Dictionary(); + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source)) + { + object value = property.GetValue(source); + if (IsOfType(value)) + { + dictionary.Add(property.Name, (T)value); + } + } + return dictionary; + } + + private static bool IsOfType(object value) + { + return value is T; + } + + private static void ThrowExceptionWhenSourceArgumentIsNull() + { + throw new NullReferenceException("Unable to convert anonymous object to a dictionary. The source anonymous object is null."); + } + + /// + /// Extension method that turns a dictionary of string and object to an ExpandoObject + ///https://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/ + /// + public static ExpandoObject ToExpando(this IDictionary dictionary) + { + var expando = new ExpandoObject(); + var expandoDic = (IDictionary)expando; + // go through the items in the dictionary and copy over the key value pairs) + foreach (var kvp in dictionary) + { + // if the value can also be turned into an ExpandoObject, then do it! + if (kvp.Value is IDictionary) + { + var expandoValue = ((IDictionary)kvp.Value).ToExpando(); + expandoDic.Add(kvp.Key, expandoValue); + } + else if (kvp.Value is ICollection) + { + // iterate through the collection and convert any strin-object dictionaries + + // along the way into expando objects + + var itemList = new List(); + foreach (var item in (ICollection)kvp.Value) + { + if (item is IDictionary) + { + var expandoItem = ((IDictionary)item).ToExpando(); + itemList.Add(expandoItem); + } + else + { + itemList.Add(item); + } + } + expandoDic.Add(kvp.Key, itemList); + } + else + { + expandoDic.Add(kvp); + } + } + return expando; + } + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Manager/IBackgroundJob.cs b/src/Hangfire.RecurringJobExtensions/Manager/IBackgroundJob.cs new file mode 100644 index 0000000..aa9d867 --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Manager/IBackgroundJob.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Hangfire.Server; + +namespace Hangfire.RecurringJobExtensions.Manager +{ + /// Interface for background job. + public interface IBackgroundJob + { + /// Executes the given context. + /// The context. + /// An asynchronous result. + Task Execute(PerformContext context); + } + + /// Interface for background job. + /// Type of the job data. + public interface IBackgroundJob where TJobData : IJobData + { + /// Executes. + /// The context. + /// A variable-length parameters list containing arguments. + /// An asynchronous result. + Task Execute(PerformContext context, params TJobData[] args); + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Manager/IJobData.cs b/src/Hangfire.RecurringJobExtensions/Manager/IJobData.cs new file mode 100644 index 0000000..2da40ac --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Manager/IJobData.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Hangfire.RecurringJobExtensions.Manager +{ + /// Interface for job data. + public interface IJobData + { + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Manager/IScheduleJobManager.cs b/src/Hangfire.RecurringJobExtensions/Manager/IScheduleJobManager.cs new file mode 100644 index 0000000..e7914b4 --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Manager/IScheduleJobManager.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Hangfire.Annotations; +using Hangfire.Server; + +namespace Hangfire.RecurringJobExtensions.Manager +{ + /// Interface for scheduzle job manager. + public interface IScheduleJobManager + { + /// Executes the schedule asynchronous operation. + /// Type of the job. + /// Type of the job data. + /// The arguments. + /// An asynchronous result.. This will never be null. + [NotNull] + Task ExecuteScheduleAsync(TJobData args) where TJob : IBackgroundJob, new() + where TJobData : IJobData; + + /// Executes the background job asynchronous operation. + /// Type of the job. + /// Type of the job data. + /// The context. + /// (Optional) The delay. + /// A variable-length parameters list containing job data. + /// An asynchronous result.. This will never be null. + [NotNull] + Task ExecuteBackgroundJobAsync(PerformContext context, TimeSpan? delay = null, + params TJobData[] jobData) + where TJob : IBackgroundJob, new() + where TJobData : IJobData; + } +} \ No newline at end of file diff --git a/src/Hangfire.RecurringJobExtensions/Manager/ScheduleJobManager.cs b/src/Hangfire.RecurringJobExtensions/Manager/ScheduleJobManager.cs new file mode 100644 index 0000000..d03f1fe --- /dev/null +++ b/src/Hangfire.RecurringJobExtensions/Manager/ScheduleJobManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Hangfire.Server; + +namespace Hangfire.RecurringJobExtensions.Manager +{ + /// Manager for schedule jobs. + public class ScheduleJobManager : IScheduleJobManager + { + /// Executes the schedule asynchronous operation. + /// Type of the job. + /// Type of the job data. + /// The arguments. + /// An asynchronous result. + public Task ExecuteScheduleAsync(TJobData args) + where TJob : IBackgroundJob, new() + where TJobData : IJobData + { + CronJob.AddOrUpdate(j => j.Execute(null), args); + + return Task.FromResult(0); + } + + /// Executes the background job asynchronous operation. + /// Type of the job. + /// Type of the job data. + /// The context. + /// (Optional) The delay. + /// A variable-length parameters list containing job data. + /// An asynchronous result. + public Task ExecuteBackgroundJobAsync(PerformContext context, TimeSpan? delay = null, params TJobData[] jobData) + where TJob : IBackgroundJob, new() + where TJobData : IJobData + { + if (!delay.HasValue) + { + BackgroundJob.Enqueue(job => job.Execute(context, jobData)); + } + else + { + BackgroundJob.Schedule(job => job.Execute(context, jobData), delay.Value); + } + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/test/Hangfire.RecurringJobExtensions.Tests/Hangfire.RecurringJobExtensions.Tests.csproj b/test/Hangfire.RecurringJobExtensions.Tests/Hangfire.RecurringJobExtensions.Tests.csproj index 1106e8d..bfb095d 100644 --- a/test/Hangfire.RecurringJobExtensions.Tests/Hangfire.RecurringJobExtensions.Tests.csproj +++ b/test/Hangfire.RecurringJobExtensions.Tests/Hangfire.RecurringJobExtensions.Tests.csproj @@ -1,21 +1,16 @@  - netcoreapp1.1 + netcoreapp2.0 Hangfire.RecurringJobExtensions.Tests Hangfire.RecurringJobExtensions.Tests true - $(PackageTargetFallback);dnxcore50 - 1.1.1 + 2.0.0 false false false - - - - @@ -23,6 +18,10 @@ + + + + Always