diff --git a/Directory.Packages.props b/Directory.Packages.props index c7c47642..968d4985 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,8 +15,8 @@ - - + + diff --git a/NuGet.config b/NuGet.config index 7c791c5c..a37acc28 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,6 @@ - diff --git a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj index 1cbfb07a..bd1fc2e3 100644 --- a/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj +++ b/src/OpenIddict.Quartz/OpenIddict.Quartz.csproj @@ -3,7 +3,6 @@ net461;netcoreapp2.1;netcoreapp3.1;netstandard2.0;netstandard2.1 enable - false diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs new file mode 100644 index 00000000..abd12daa --- /dev/null +++ b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs @@ -0,0 +1,46 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using Microsoft.Extensions.Options; +using Quartz; +using SR = OpenIddict.Abstractions.OpenIddictResources; + +namespace OpenIddict.Quartz +{ + /// + /// Contains the methods required to ensure that the OpenIddict Quartz.NET configuration is valid. + /// + public class OpenIddictQuartzConfiguration : IConfigureOptions + { + /// + public void Configure(QuartzOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.AddJob(builder => + { + builder.StoreDurably() + .WithIdentity(OpenIddictQuartzJob.Identity) + .WithDescription(SR.GetResourceString(SR.ID8000)); + }); + + options.AddTrigger(builder => + { + // Note: this trigger uses a quite long interval (1 hour), which means it may be potentially + // never reached if the application is shut down or recycled. As such, this trigger is set up + // to fire 2 minutes after the application starts to ensure it's executed at least once. + builder.ForJob(OpenIddictQuartzJob.Identity) + .WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) + .WithDescription(SR.GetResourceString(SR.ID8001)) + .StartAt(DateBuilder.FutureDate(2, IntervalUnit.Minute)); + }); + } + } +} diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs index 8883ed56..d2d3d501 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs @@ -5,11 +5,10 @@ */ using System; -using System.Linq; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using OpenIddict.Quartz; using Quartz; -using SR = OpenIddict.Abstractions.OpenIddictResources; namespace Microsoft.Extensions.DependencyInjection { @@ -31,44 +30,15 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - // Warning: the AddQuartz() method is deliberately not used as it's not idempotent. - // Calling it at this point may override user-defined services (e.g Quartz DI support). + builder.Services.AddQuartz(); + // The OpenIddict job is registered as a service to allow + // Quartz.NET's DI integration to resolve it from the DI. builder.Services.TryAddTransient(); - // To ensure this method can be safely called multiple times, the job details - // of the OpenIddict job are only added if no existing IJobDetail instance - // pointing to OpenIddictQuartzJob was already registered in the DI container. - if (!builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IJobDetail) && - descriptor.ImplementationInstance is IJobDetail job && - job.Key.Equals(OpenIddictQuartzJob.Identity))) - { - builder.Services.AddSingleton( - JobBuilder.Create() - .StoreDurably() - .WithIdentity(OpenIddictQuartzJob.Identity) - .WithDescription(SR.GetResourceString(SR.ID8000)) - .Build()); - } - - // To ensure this method can be safely called multiple times, the trigger details - // of the OpenIddict job are only added if no existing ITrigger instance - // pointing to OpenIddictQuartzJob was already registered in the DI container. - if (!builder.Services.Any(descriptor => descriptor.ServiceType == typeof(ITrigger) && - descriptor.ImplementationInstance is ITrigger trigger && - trigger.JobKey.Equals(OpenIddictQuartzJob.Identity))) - { - // Note: this trigger uses a quite long interval (1 hour), which means it may be potentially - // never reached if the application is shut down or recycled. As such, this trigger is set up - // to fire 2 minutes after the application starts to ensure it's executed at least once. - builder.Services.AddSingleton( - TriggerBuilder.Create() - .ForJob(OpenIddictQuartzJob.Identity) - .WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) - .WithDescription(SR.GetResourceString(SR.ID8001)) - .StartAt(DateBuilder.FutureDate(2, IntervalUnit.Minute)) - .Build()); - } + // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< + IConfigureOptions, OpenIddictQuartzConfiguration>()); return new OpenIddictQuartzBuilder(builder.Services); } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs new file mode 100644 index 00000000..37bd3bf8 --- /dev/null +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs @@ -0,0 +1,38 @@ +using Quartz; +using Xunit; + +namespace OpenIddict.Quartz.Tests +{ + public class OpenIddictQuartzConfigurationTests + { + [Fact] + public void UseQuartz_RegistersJobDetails() + { + // Arrange + var options = new QuartzOptions(); + var configuration = new OpenIddictQuartzConfiguration(); + + // Act + configuration.Configure(options); + + // Assert + // TODO: uncomment when JobDetails and Triggers are publicly exposed. + // Assert.Contains(options.JobDetails, job => job.Key.Equals(OpenIddictQuartzJob.Identity)); + } + + [Fact] + public void UseQuartz_RegistersTriggerDetails() + { + // Arrange + var options = new QuartzOptions(); + var configuration = new OpenIddictQuartzConfiguration(); + + // Act + configuration.Configure(options); + + // Assert + // TODO: uncomment when JobDetails and Triggers are publicly exposed. + // Assert.Contains(options.Triggers, trigger => trigger.JobKey.Equals(OpenIddictQuartzJob.Identity)); + } + } +} diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs index 7ec1dc5d..127e27d2 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs @@ -1,5 +1,6 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Quartz; using Xunit; @@ -48,38 +49,6 @@ namespace OpenIddict.Quartz.Tests service.Lifetime == ServiceLifetime.Transient); } - [Fact] - public void UseQuartz_RegistersJobDetails() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseQuartz(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IJobDetail) && - service.ImplementationInstance is IJobDetail job && - job.Key.Equals(OpenIddictQuartzJob.Identity)); - } - - [Fact] - public void UseQuartz_RegistersTriggerDetails() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseQuartz(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(ITrigger) && - service.ImplementationInstance is ITrigger trigger && - trigger.JobKey.Equals(OpenIddictQuartzJob.Identity)); - } - [Fact] public void UseQuartz_CanBeSafelyInvokedMultipleTimes() { @@ -93,13 +62,13 @@ namespace OpenIddict.Quartz.Tests builder.UseQuartz(); // Assert - Assert.Single(services, service => service.ServiceType == typeof(IJobDetail) && - service.ImplementationInstance is IJobDetail job && - job.Key.Equals(OpenIddictQuartzJob.Identity)); + Assert.Single(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && + service.ImplementationType == typeof(OpenIddictQuartzJob) && + service.Lifetime == ServiceLifetime.Transient); - Assert.Single(services, service => service.ServiceType == typeof(ITrigger) && - service.ImplementationInstance is ITrigger trigger && - trigger.JobKey.Equals(OpenIddictQuartzJob.Identity)); + Assert.Single(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationType == typeof(OpenIddictQuartzConfiguration) && + service.Lifetime == ServiceLifetime.Singleton); } } }